self.nodes[node] = properties
        else:
            self.nodes[node].update(properties)
[docs]    def return_node_list(self) -> List[Node]:
        """
        Returns the nodes as a list of Node objects.
        Each Node object will have its ID, type, and properties populated.
        Returns:
            List[Node]: A list of Node objects.
        """
        nodes = [
            Node(id=key[0], type=key[1], properties=self.nodes[key])
            for key in self.nodes
        ]
        return nodes
# Properties that should be treated as node properties instead of relationships
FACT_TO_PROPERTY_TYPE = [
    "Date",
    "Number",
    "Job title",
    "Cause of death",
    "Organization type",
    "Academic title",
]
schema_mapping = [
    ("HEADQUARTERS", "ORGANIZATION_LOCATIONS"),
    ("RESIDENCE", "PERSON_LOCATION"),
    ("ALL_PERSON_LOCATIONS", "PERSON_LOCATION"),
    ("CHILD", "HAS_CHILD"),
    ("PARENT", "HAS_PARENT"),
    ("CUSTOMERS", "HAS_CUSTOMER"),
    ("SKILLED_AT", "INTERESTED_IN"),
]
[docs]class SimplifiedSchema:
    """
    Provides functionality for working with a simplified schema mapping.
    Attributes:
        schema (Dict): A dictionary containing the mapping to simplified schema types.
    """
[docs]    def __init__(self) -> None:
        """Initializes the schema dictionary based on the predefined list."""
        self.schema = dict()
        for row in schema_mapping:
            self.schema[row[0]] = row[1]
[docs]    def get_type(self, type: str) -> str:
        """


[docs]    def get_type(self, type: str) -> str:
        """
        Retrieves the simplified schema type for a given original type.
        Args:
            type (str): The original schema type to find the simplified type for.
        Returns:
            str: The simplified schema type if it exists;
                 otherwise, returns the original type.
        """
        try:
            return self.schema[type]
        except KeyError:
            return type
[docs]class DiffbotGraphTransformer:
    """Transforms documents into graph documents using Diffbot's NLP API.
    A graph document transformation system takes a sequence of Documents and returns a
    sequence of Graph Documents.
    Example:
        .. code-block:: python
            class DiffbotGraphTransformer(BaseGraphDocumentTransformer):
                def transform_documents(
                    self, documents: Sequence[Document], **kwargs: Any
                ) -> Sequence[GraphDocument]:
                    results = []
                    for document in documents:
                        raw_results = self.nlp_request(document.page_content)
                        graph_document = self.process_response(raw_results, document)
                        results.append(graph_document)
                    return results
                async def atransform_documents(
                    self, documents: Sequence[Document], **kwargs: Any
                ) -> Sequence[Document]:
                    raise NotImplementedError
    """
[docs]    def __init__(
        self,
        diffbot_api_key: Optional[str] = None,
        fact_confidence_threshold: float = 0.7,
        include_qualifiers: bool = True,
        include_evidence: bool = True,
        simplified_schema: bool = True,
    ) -> None:
        """
        Initialize the graph transformer with various options.
        Args:
            diffbot_api_key (str):


Args:
            diffbot_api_key (str):
               The API key for Diffbot's NLP services.
            fact_confidence_threshold (float):
                Minimum confidence level for facts to be included.
            include_qualifiers (bool):
                Whether to include qualifiers in the relationships.
            include_evidence (bool):
                Whether to include evidence for the relationships.
            simplified_schema (bool):
                Whether to use a simplified schema for relationships.
        """
        self.diffbot_api_key = diffbot_api_key or get_from_env(
            "diffbot_api_key", "DIFFBOT_API_KEY"
        )
        self.fact_threshold_confidence = fact_confidence_threshold
        self.include_qualifiers = include_qualifiers
        self.include_evidence = include_evidence
        self.simplified_schema = None
        if simplified_schema:
            self.simplified_schema = SimplifiedSchema()
[docs]    def nlp_request(self, text: str) -> Dict[str, Any]:
        """
        Make an API request to the Diffbot NLP endpoint.
        Args:
            text (str): The text to be processed.
        Returns:
            Dict[str, Any]: The JSON response from the API.
        """
        # Relationship extraction only works for English
        payload = {
            "content": text,
            "lang": "en",
        }
        FIELDS = "facts"
        HOST = "nl.diffbot.com"
        url = (
            f"https://{HOST}/v1/?fields={FIELDS}&"
            f"token={self.diffbot_api_key}&language=en"
        )
        result = requests.post(url, data=payload)
        return result.json()
[docs]    def process_response(
        self, payload: Dict[str, Any], document: Document


self, payload: Dict[str, Any], document: Document
    ) -> GraphDocument:
        """
        Transform the Diffbot NLP response into a GraphDocument.
        Args:
            payload (Dict[str, Any]): The JSON response from Diffbot's NLP API.
            document (Document): The original document.
        Returns:
            GraphDocument: The transformed document as a graph.
        """
        # Return empty result if there are no facts
        if "facts" not in payload or not payload["facts"]:
            return GraphDocument(nodes=[], relationships=[], source=document)
        # Nodes are a custom class because we need to deduplicate
        nodes_list = NodesList()
        # Relationships are a list because we don't deduplicate nor anything else
        relationships = list()
        for record in payload["facts"]:
            # Skip if the fact is below the threshold confidence
            if record["confidence"] < self.fact_threshold_confidence:
                continue
            # TODO: It should probably be treated as a node property
            if not record["value"]["allTypes"]:
                continue
            # Define source node
            source_id = (
                record["entity"]["allUris"][0]
                if record["entity"]["allUris"]
                else record["entity"]["name"]
            )
            source_label = record["entity"]["allTypes"][0]["name"].capitalize()
            source_name = record["entity"]["name"]
            source_node = Node(id=source_id, type=source_label)
            nodes_list.add_node_property(
                (source_id, source_label), {"name": source_name}
            )
            # Define target node
            target_id = (
                record["value"]["allUris"][0]
                if record["value"]["allUris"]


if record["value"]["allUris"]
                else record["value"]["name"]
            )
            target_label = record["value"]["allTypes"][0]["name"].capitalize()
            target_name = record["value"]["name"]
            # Some facts are better suited as node properties
            if target_label in FACT_TO_PROPERTY_TYPE:
                nodes_list.add_node_property(
                    (source_id, source_label),
                    {format_property_key(record["property"]["name"]): target_name},
                )
            else:  # Define relationship
                # Define target node object
                target_node = Node(id=target_id, type=target_label)
                nodes_list.add_node_property(
                    (target_id, target_label), {"name": target_name}
                )
                # Define relationship type
                rel_type = record["property"]["name"].replace(" ", "_").upper()
                if self.simplified_schema:
                    rel_type = self.simplified_schema.get_type(rel_type)
                # Relationship qualifiers/properties
                rel_properties = dict()
                relationship_evidence = [el["passage"] for el in record["evidence"]][0]
                if self.include_evidence:
                    rel_properties.update({"evidence": relationship_evidence})
                if self.include_qualifiers and record.get("qualifiers"):
                    for property in record["qualifiers"]:
                        prop_key = format_property_key(property["property"]["name"])
                        rel_properties[prop_key] = property["value"]["name"]
                relationship = Relationship(
                    source=source_node,
                    target=target_node,
                    type=rel_type,
                    properties=rel_properties,
                )
                relationships.append(relationship)
        return GraphDocument(
            nodes=nodes_list.return_node_list(),
            relationships=relationships,
            source=document,
        )


relationships=relationships,
            source=document,
        )
[docs]    def convert_to_graph_documents(
        self, documents: Sequence[Document]
    ) -> List[GraphDocument]:
        """Convert a sequence of documents into graph documents.
        Args:
            documents (Sequence[Document]): The original documents.
            **kwargs: Additional keyword arguments.
        Returns:
            Sequence[GraphDocument]: The transformed documents as graphs.
        """
        results = []
        for document in documents:
            raw_results = self.nlp_request(document.page_content)
            graph_document = self.process_response(raw_results, document)
            results.append(graph_document)
        return results


Source code for langchain_experimental.prompt_injection_identifier.hugging_face_identifier
"""Tool for the identification of prompt injection attacks."""
from __future__ import annotations
from typing import TYPE_CHECKING
from langchain.pydantic_v1 import Field
from langchain.tools.base import BaseTool
if TYPE_CHECKING:
    from transformers import Pipeline
def _model_default_factory() -> Pipeline:
    try:
        from transformers import pipeline
    except ImportError as e:
        raise ImportError(
            "Cannot import transformers, please install with "
            "`pip install transformers`."
        ) from e
    return pipeline("text-classification", model="deepset/deberta-v3-base-injection")
[docs]class HuggingFaceInjectionIdentifier(BaseTool):
    """Tool that uses deberta-v3-base-injection to detect prompt injection attacks."""
    name: str = "hugging_face_injection_identifier"
    description: str = (
        "A wrapper around HuggingFace Prompt Injection security model. "
        "Useful for when you need to ensure that prompt is free of injection attacks. "
        "Input should be any message from the user."
    )
    model: Pipeline = Field(default_factory=_model_default_factory)
    def _run(self, query: str) -> str:
        """Use the tool."""
        result = self.model(query)
        result = sorted(result, key=lambda x: x["score"], reverse=True)
        if result[0]["label"] == "INJECTION":
            raise ValueError("Prompt injection attack detected")
        return query


Source code for langchain_experimental.cpal.constants
from enum import Enum
[docs]class Constant(Enum):
    """Enum for constants used in the CPAL."""
    narrative_input = "narrative_input"
    chain_answer = "chain_answer"  # natural language answer
    chain_data = "chain_data"  # pydantic instance


Source code for langchain_experimental.comprehend_moderation.amazon_comprehend_moderation
from typing import Any, Dict, List, Optional
from langchain.callbacks.manager import CallbackManagerForChainRun
from langchain.chains.base import Chain
from langchain_experimental.comprehend_moderation.base_moderation import BaseModeration
from langchain_experimental.comprehend_moderation.base_moderation_callbacks import (
    BaseModerationCallbackHandler,
)
from langchain_experimental.comprehend_moderation.base_moderation_config import (
    BaseModerationConfig,
)
from langchain_experimental.pydantic_v1 import root_validator
[docs]class AmazonComprehendModerationChain(Chain):
    """A subclass of Chain, designed to apply moderation to LLMs."""
    output_key: str = "output"  #: :meta private:
    """Key used to fetch/store the output in data containers. Defaults to `output`"""
    input_key: str = "input"  #: :meta private:
    """Key used to fetch/store the input in data containers. Defaults to `input`"""
    moderation_config: BaseModerationConfig = BaseModerationConfig()
    """
    Configuration settings for moderation,
    defaults to BaseModerationConfig with default values
    """
    client: Optional[Any] = None
    """boto3 client object for connection to Amazon Comprehend"""
    region_name: Optional[str] = None
    """The aws region e.g., `us-west-2`. Fallsback to AWS_DEFAULT_REGION env variable
    or region specified in ~/.aws/config in case it is not provided here.
    """
    credentials_profile_name: Optional[str] = None
    """The name of the profile in the ~/.aws/credentials or ~/.aws/config files, which


has either access keys or role information specified.
    If not specified, the default credential profile or, if on an EC2 instance,
    credentials from IMDS will be used.
    See: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html
    """
    moderation_callback: Optional[BaseModerationCallbackHandler] = None
    """Callback handler for moderation, this is different
    from regular callbacks which can be used in addition to this."""
    unique_id: Optional[str] = None
    """A unique id that can be used to identify or group a user or session"""
    @root_validator(pre=True)
    def create_client(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        """
        Creates an Amazon Comprehend client
        Args:
            values (Dict[str, Any]): A dictionary containing configuration values.
        Returns:
            Dict[str, Any]: A dictionary with the updated configuration values,
                            including the Amazon Comprehend client.
        Raises:
            ModuleNotFoundError: If the 'boto3' package is not installed.
            ValueError: If there is an issue importing 'boto3' or loading
                        AWS credentials.
        Example:
        .. code-block:: python
            config = {
                "credentials_profile_name": "my-profile",
                "region_name": "us-west-2"
            }
            updated_config = create_client(config)
            comprehend_client = updated_config["client"]
        """
        if values.get("client") is not None:
            return values
        try:
            import boto3
            if values.get("credentials_profile_name"):
                session = boto3.Session(profile_name=values["credentials_profile_name"])
            else:
                # use default credentials
                session = boto3.Session()


else:
                # use default credentials
                session = boto3.Session()
            client_params = {}
            if values.get("region_name"):
                client_params["region_name"] = values["region_name"]
            values["client"] = session.client("comprehend", **client_params)
            return values
        except ImportError:
            raise ModuleNotFoundError(
                "Could not import boto3 python package. "
                "Please install it with `pip install boto3`."
            )
        except Exception as e:
            raise ValueError(
                "Could not load credentials to authenticate with AWS client. "
                "Please check that credentials in the specified "
                "profile name are valid."
            ) from e
    @property
    def output_keys(self) -> List[str]:
        """
        Returns a list of output keys.
        This method defines the output keys that will be used to access the output
        values produced by the chain or function. It ensures that the specified keys
        are available to access the outputs.
        Returns:
            List[str]: A list of output keys.
        Note:
            This method is considered private and may not be intended for direct
            external use.
        """
        return [self.output_key]
    @property
    def input_keys(self) -> List[str]:
        """
        Returns a list of input keys expected by the prompt.
        This method defines the input keys that the prompt expects in order to perform
        its processing. It ensures that the specified keys are available for providing
        input to the prompt.
        Returns:
            List[str]: A list of input keys.
        Note:
            This method is considered private and may not be intended for direct
            external use.
        """
        return [self.input_key]
    def _call(


"""
        return [self.input_key]
    def _call(
        self,
        inputs: Dict[str, Any],
        run_manager: Optional[CallbackManagerForChainRun] = None,
    ) -> Dict[str, str]:
        """
        Executes the moderation process on the input text and returns the processed
        output.
        This internal method performs the moderation process on the input text. It
        converts the input prompt value to plain text, applies the specified filters,
        and then converts the filtered output back to a suitable prompt value object.
        Additionally, it provides the option to log information about the run using
        the provided `run_manager`.
        Args:
            inputs: A dictionary containing input values
            run_manager: A run manager to handle run-related events. Default is None
        Returns:
            Dict[str, str]: A dictionary containing the processed output of the
                            moderation process.
        Raises:
            ValueError: If there is an error during the moderation process
        """
        if run_manager:
            run_manager.on_text("Running AmazonComprehendModerationChain...\n")
        moderation = BaseModeration(
            client=self.client,
            config=self.moderation_config,
            moderation_callback=self.moderation_callback,
            unique_id=self.unique_id,
            run_manager=run_manager,
        )
        response = moderation.moderate(prompt=inputs[self.input_keys[0]])
        return {self.output_key: response}


Source code for langchain_experimental.comprehend_moderation.pii
import asyncio
from typing import Any, Dict, Optional
from langchain_experimental.comprehend_moderation.base_moderation_exceptions import (
    ModerationPiiError,
)
[docs]class ComprehendPII:
[docs]    def __init__(
        self,
        client: Any,
        callback: Optional[Any] = None,
        unique_id: Optional[str] = None,
        chain_id: Optional[str] = None,
    ) -> None:
        self.client = client
        self.moderation_beacon = {
            "moderation_chain_id": chain_id,
            "moderation_type": "PII",
            "moderation_status": "LABELS_NOT_FOUND",
        }
        self.callback = callback
        self.unique_id = unique_id
[docs]    def validate(self, prompt_value: str, config: Any = None) -> str:
        redact = config.get("redact")
        return (
            self._detect_pii(prompt_value=prompt_value, config=config)
            if redact
            else self._contains_pii(prompt_value=prompt_value, config=config)
        )
    def _contains_pii(self, prompt_value: str, config: Any = None) -> str:
        """
        Checks for Personally Identifiable Information (PII) labels above a
        specified threshold. Uses Amazon Comprehend Contains PII Entities API. See -
        https://docs.aws.amazon.com/comprehend/latest/APIReference/API_ContainsPiiEntities.html
        Args:
            prompt_value (str): The input text to be checked for PII labels.
            config (Dict[str, Any]): Configuration for PII check and actions.
        Returns:


Returns:
            str: the original prompt
        Note:
            - The provided client should be initialized with valid AWS credentials.
        """
        pii_identified = self.client.contains_pii_entities(
            Text=prompt_value, LanguageCode="en"
        )
        if self.callback and self.callback.pii_callback:
            self.moderation_beacon["moderation_input"] = prompt_value
            self.moderation_beacon["moderation_output"] = pii_identified
        threshold = config.get("threshold")
        pii_labels = config.get("labels")
        pii_found = False
        for entity in pii_identified["Labels"]:
            if (entity["Score"] >= threshold and entity["Name"] in pii_labels) or (
                entity["Score"] >= threshold and not pii_labels
            ):
                pii_found = True
                break
        if self.callback and self.callback.pii_callback:
            if pii_found:
                self.moderation_beacon["moderation_status"] = "LABELS_FOUND"
            asyncio.create_task(
                self.callback.on_after_pii(self.moderation_beacon, self.unique_id)
            )
        if pii_found:
            raise ModerationPiiError
        return prompt_value
    def _detect_pii(self, prompt_value: str, config: Optional[Dict[str, Any]]) -> str:
        """
        Detects and handles Personally Identifiable Information (PII) entities in the
        given prompt text using Amazon Comprehend's detect_pii_entities API. The
        function provides options to redact or stop processing based on the identified
        PII entities and a provided configuration. Uses Amazon Comprehend Detect PII
        Entities API.
        Args:
            prompt_value (str): The input text to be checked for PII entities.


prompt_value (str): The input text to be checked for PII entities.
            config (Dict[str, Any]): A configuration specifying how to handle
                                     PII entities.
        Returns:
            str: The processed prompt text with redacted PII entities or raised
                 exceptions.
        Raises:
            ValueError: If the prompt contains configured PII entities for
                        stopping processing.
        Note:
            - If PII is not found in the prompt, the original prompt is returned.
            - The client should be initialized with valid AWS credentials.
        """
        pii_identified = self.client.detect_pii_entities(
            Text=prompt_value, LanguageCode="en"
        )
        if self.callback and self.callback.pii_callback:
            self.moderation_beacon["moderation_input"] = prompt_value
            self.moderation_beacon["moderation_output"] = pii_identified
        if (pii_identified["Entities"]) == []:
            if self.callback and self.callback.pii_callback:
                asyncio.create_task(
                    self.callback.on_after_pii(self.moderation_beacon, self.unique_id)
                )
            return prompt_value
        pii_found = False
        if not config and pii_identified["Entities"]:
            for entity in pii_identified["Entities"]:
                if entity["Score"] >= 0.5:
                    pii_found = True
                    break
            if self.callback and self.callback.pii_callback:
                if pii_found:
                    self.moderation_beacon["moderation_status"] = "LABELS_FOUND"
                asyncio.create_task(
                    self.callback.on_after_pii(self.moderation_beacon, self.unique_id)
                )
            if pii_found:
                raise ModerationPiiError
        else:


if pii_found:
                raise ModerationPiiError
        else:
            threshold = config.get("threshold")  # type: ignore
            pii_labels = config.get("labels")  # type: ignore
            mask_marker = config.get("mask_character")  # type: ignore
            pii_found = False
            for entity in pii_identified["Entities"]:
                if (
                    pii_labels
                    and entity["Type"] in pii_labels
                    and entity["Score"] >= threshold
                ) or (not pii_labels and entity["Score"] >= threshold):
                    pii_found = True
                    char_offset_begin = entity["BeginOffset"]
                    char_offset_end = entity["EndOffset"]
                    mask_length = char_offset_end - char_offset_begin + 1
                    masked_part = mask_marker * mask_length
                    prompt_value = (
                        prompt_value[:char_offset_begin]
                        + masked_part
                        + prompt_value[char_offset_end + 1 :]
                    )
            if self.callback and self.callback.pii_callback:
                if pii_found:
                    self.moderation_beacon["moderation_status"] = "LABELS_FOUND"
                asyncio.create_task(
                    self.callback.on_after_pii(self.moderation_beacon, self.unique_id)
                )
        return prompt_value


Source code for langchain_experimental.comprehend_moderation.base_moderation_config
from typing import List, Union
from pydantic import BaseModel
[docs]class ModerationPiiConfig(BaseModel):
    threshold: float = 0.5
    """Threshold for PII confidence score, defaults to 0.5 i.e. 50%"""
    labels: List[str] = []
    """
    List of PII Universal Labels.
    Defaults to `list[]`
    """
    redact: bool = False
    """Whether to perform redaction of detected PII entities"""
    mask_character: str = "*"
    """Redaction mask character in case redact=True, defaults to asterisk (*)"""
[docs]class ModerationToxicityConfig(BaseModel):
    threshold: float = 0.5
    """Threshold for Toxic label confidence score, defaults to 0.5 i.e. 50%"""
    labels: List[str] = []
    """List of toxic labels, defaults to `list[]`"""
[docs]class ModerationIntentConfig(BaseModel):
    threshold: float = 0.5
    """
    Threshold for Intent classification
    confidence score, defaults to 0.5 i.e. 50%
    """
[docs]class BaseModerationConfig(BaseModel):
    filters: List[
        Union[ModerationPiiConfig, ModerationToxicityConfig, ModerationIntentConfig]
    ] = [
        ModerationPiiConfig(),
        ModerationToxicityConfig(),
        ModerationIntentConfig(),
    ]
    """
    Filters applied to the moderation chain, defaults to
    `[ModerationPiiConfig(), ModerationToxicityConfig(),
    ModerationIntentConfig()]`
    """


Source code for langchain_experimental.comprehend_moderation.base_moderation_callbacks
from typing import Any, Callable, Dict
[docs]class BaseModerationCallbackHandler:
[docs]    def __init__(self) -> None:
        if (
            self._is_method_unchanged(
                BaseModerationCallbackHandler.on_after_pii, self.on_after_pii
            )
            and self._is_method_unchanged(
                BaseModerationCallbackHandler.on_after_toxicity, self.on_after_toxicity
            )
            and self._is_method_unchanged(
                BaseModerationCallbackHandler.on_after_intent, self.on_after_intent
            )
        ):
            raise NotImplementedError(
                "Subclasses must override at least one of on_after_pii(), "
                "on_after_toxicity(), or on_after_intent() functions."
            )
    def _is_method_unchanged(
        self, base_method: Callable, derived_method: Callable
    ) -> bool:
        return base_method.__qualname__ == derived_method.__qualname__
[docs]    async def on_after_pii(
        self, moderation_beacon: Dict[str, Any], unique_id: str, **kwargs: Any
    ) -> None:
        """Run after PII validation is complete."""
        pass
[docs]    async def on_after_toxicity(
        self, moderation_beacon: Dict[str, Any], unique_id: str, **kwargs: Any
    ) -> None:
        """Run after Toxicity validation is complete."""
        pass
[docs]    async def on_after_intent(
        self, moderation_beacon: Dict[str, Any], unique_id: str, **kwargs: Any
    ) -> None:
        """Run after Toxicity validation is complete."""


) -> None:
        """Run after Toxicity validation is complete."""
        pass
    @property
    def pii_callback(self) -> bool:
        return (
            self.on_after_pii.__func__  # type: ignore
            is not BaseModerationCallbackHandler.on_after_pii
        )
    @property
    def toxicity_callback(self) -> bool:
        return (
            self.on_after_toxicity.__func__  # type: ignore
            is not BaseModerationCallbackHandler.on_after_toxicity
        )
    @property
    def intent_callback(self) -> bool:
        return (
            self.on_after_intent.__func__  # type: ignore
            is not BaseModerationCallbackHandler.on_after_intent
        )


Source code for langchain_experimental.comprehend_moderation.toxicity
import asyncio
import importlib
from typing import Any, List, Optional
from langchain_experimental.comprehend_moderation.base_moderation_exceptions import (
    ModerationToxicityError,
)
[docs]class ComprehendToxicity:
[docs]    def __init__(
        self,
        client: Any,
        callback: Optional[Any] = None,
        unique_id: Optional[str] = None,
        chain_id: Optional[str] = None,
    ) -> None:
        self.client = client
        self.moderation_beacon = {
            "moderation_chain_id": chain_id,
            "moderation_type": "Toxicity",
            "moderation_status": "LABELS_NOT_FOUND",
        }
        self.callback = callback
        self.unique_id = unique_id
    def _toxicity_init_validate(self, max_size: int) -> Any:
        """
        Validate and initialize toxicity processing configuration.
        Args:
            max_size (int): Maximum sentence size defined in the
            configuration object.
        Raises:
            Exception: If the maximum sentence size exceeds the 5KB limit.
        Note:
            This function ensures that the NLTK punkt tokenizer is downloaded
            if not already present.
        Returns:
            None
        """
        if max_size > 1024 * 5:
            raise Exception("The sentence length should not exceed 5KB.")
        try:
            nltk = importlib.import_module("nltk")
            nltk.data.find("tokenizers/punkt")
            return nltk
        except ImportError:
            raise ModuleNotFoundError(
                "Could not import nltk python package. "


raise ModuleNotFoundError(
                "Could not import nltk python package. "
                "Please install it with `pip install nltk`."
            )
        except LookupError:
            nltk.download("punkt")
    def _split_paragraph(
        self, prompt_value: str, max_size: int = 1024 * 4
    ) -> List[List[str]]:
        """
        Split a paragraph into chunks of sentences, respecting the maximum size limit.
        Args:
            paragraph (str): The input paragraph to be split into chunks.
            max_size (int, optional): The maximum size limit in bytes for
            each chunk. Defaults to 1024.
        Returns:
            List[List[str]]: A list of chunks, where each chunk is a list
            of sentences.
        Note:
            This function validates the maximum sentence size based on service
            limits using the 'toxicity_init_validate' function. It uses the NLTK
            sentence tokenizer to split the paragraph into sentences.
        Example:
            paragraph = "This is a sample paragraph. It
            contains multiple sentences. ..."
            chunks = split_paragraph(paragraph, max_size=2048)
        """
        # validate max. sentence size based on Service limits
        nltk = self._toxicity_init_validate(max_size)
        sentences = nltk.sent_tokenize(prompt_value)
        chunks = list()  # type: ignore
        current_chunk = list()  # type: ignore
        current_size = 0
        for sentence in sentences:
            sentence_size = len(sentence.encode("utf-8"))
            # If adding a new sentence exceeds max_size
            # or current_chunk has 10 sentences, start a new chunk
            if (current_size + sentence_size > max_size) or (len(current_chunk) >= 10):


if current_chunk:  # Avoid appending empty chunks
                    chunks.append(current_chunk)
                current_chunk = []
                current_size = 0
            current_chunk.append(sentence)
            current_size += sentence_size
        # Add any remaining sentences
        if current_chunk:
            chunks.append(current_chunk)
        return chunks
[docs]    def validate(self, prompt_value: str, config: Any = None) -> str:
        """
        Check the toxicity of a given text prompt using AWS
        Comprehend service and apply actions based on configuration.
        Args:
            prompt_value (str): The text content to be checked for toxicity.
            config (Dict[str, Any]): Configuration for toxicity checks and actions.
        Returns:
            str: The original prompt_value if allowed or no toxicity found.
        Raises:
            ValueError: If the prompt contains toxic labels and cannot be
            processed based on the configuration.
        """
        chunks = self._split_paragraph(prompt_value=prompt_value)
        for sentence_list in chunks:
            segments = [{"Text": sentence} for sentence in sentence_list]
            response = self.client.detect_toxic_content(
                TextSegments=segments, LanguageCode="en"
            )
            if self.callback and self.callback.toxicity_callback:
                self.moderation_beacon["moderation_input"] = segments  # type: ignore
                self.moderation_beacon["moderation_output"] = response
            toxicity_found = False
            threshold = config.get("threshold")
            toxicity_labels = config.get("labels")
            if not toxicity_labels:
                for item in response["ResultList"]:
                    for label in item["Labels"]:
                        if label["Score"] >= threshold:
                            toxicity_found = True
                            break
            else:


toxicity_found = True
                            break
            else:
                for item in response["ResultList"]:
                    for label in item["Labels"]:
                        if (
                            label["Name"] in toxicity_labels
                            and label["Score"] >= threshold
                        ):
                            toxicity_found = True
                            break
            if self.callback and self.callback.toxicity_callback:
                if toxicity_found:
                    self.moderation_beacon["moderation_status"] = "LABELS_FOUND"
                asyncio.create_task(
                    self.callback.on_after_toxicity(
                        self.moderation_beacon, self.unique_id
                    )
                )
            if toxicity_found:
                raise ModerationToxicityError
        return prompt_value


Source code for langchain_experimental.comprehend_moderation.intent
import asyncio
from typing import Any, Optional
from langchain_experimental.comprehend_moderation.base_moderation_exceptions import (
    ModerationIntentionError,
)
[docs]class ComprehendIntent:
[docs]    def __init__(
        self,
        client: Any,
        callback: Optional[Any] = None,
        unique_id: Optional[str] = None,
        chain_id: Optional[str] = None,
    ) -> None:
        self.client = client
        self.moderation_beacon = {
            "moderation_chain_id": chain_id,
            "moderation_type": "Intent",
            "moderation_status": "LABELS_NOT_FOUND",
        }
        self.callback = callback
        self.unique_id = unique_id
    def _get_arn(self) -> str:
        region_name = self.client.meta.region_name
        service = "comprehend"
        intent_endpoint = "document-classifier-endpoint/prompt-intent"
        return f"arn:aws:{service}:{region_name}:aws:{intent_endpoint}"
[docs]    def validate(self, prompt_value: str, config: Any = None) -> str:
        """
        Check and validate the intent of the given prompt text.
        Args:
            prompt_value (str): The input text to be checked for unintended intent.
            config (Dict[str, Any]): Configuration settings for intent checks.
        Raises:
            ValueError: If unintended intent is found in the prompt text based
            on the specified threshold.
        Returns:
            str: The input prompt_value.
        Note:
            This function checks the intent of the provided prompt text using
            Comprehend's classify_document API and raises an error if unintended


Comprehend's classify_document API and raises an error if unintended
            intent is detected with a score above the specified threshold.
        Example:
            comprehend_client = boto3.client('comprehend')
            prompt_text = "Please tell me your credit card information."
            config = {"threshold": 0.7}
            checked_prompt = check_intent(comprehend_client, prompt_text, config)
        """
        threshold = config.get("threshold")
        intent_found = False
        endpoint_arn = self._get_arn()
        response = self.client.classify_document(
            Text=prompt_value, EndpointArn=endpoint_arn
        )
        if self.callback and self.callback.intent_callback:
            self.moderation_beacon["moderation_input"] = prompt_value
            self.moderation_beacon["moderation_output"] = response
        for class_result in response["Classes"]:
            if (
                class_result["Score"] >= threshold
                and class_result["Name"] == "UNDESIRED_PROMPT"
            ):
                intent_found = True
                break
        if self.callback and self.callback.intent_callback:
            if intent_found:
                self.moderation_beacon["moderation_status"] = "LABELS_FOUND"
            asyncio.create_task(
                self.callback.on_after_intent(self.moderation_beacon, self.unique_id)
            )
        if intent_found:
            raise ModerationIntentionError
        return prompt_value


Source code for langchain_experimental.comprehend_moderation.base_moderation
import uuid
from typing import Any, Callable, Optional
from langchain.callbacks.manager import CallbackManagerForChainRun
from langchain.prompts.base import StringPromptValue
from langchain.prompts.chat import ChatPromptValue
from langchain.schema import AIMessage, HumanMessage
from langchain_experimental.comprehend_moderation.intent import ComprehendIntent
from langchain_experimental.comprehend_moderation.pii import ComprehendPII
from langchain_experimental.comprehend_moderation.toxicity import ComprehendToxicity
[docs]class BaseModeration:
[docs]    def __init__(
        self,
        client: Any,
        config: Optional[Any] = None,
        moderation_callback: Optional[Any] = None,
        unique_id: Optional[str] = None,
        run_manager: Optional[CallbackManagerForChainRun] = None,
    ):
        self.client = client
        self.config = config
        self.moderation_callback = moderation_callback
        self.unique_id = unique_id
        self.chat_message_index = 0
        self.run_manager = run_manager
        self.chain_id = str(uuid.uuid4())
    def _convert_prompt_to_text(self, prompt: Any) -> str:
        input_text = str()
        if isinstance(prompt, StringPromptValue):
            input_text = prompt.text
        elif isinstance(prompt, str):
            input_text = prompt
        elif isinstance(prompt, ChatPromptValue):
            """
            We will just check the last message in the message Chain of a
            ChatPromptTemplate. The typical chronology is
            SystemMessage > HumanMessage > AIMessage and so on. However assuming
            that with every chat the chain is invoked we will only check the last


that with every chat the chain is invoked we will only check the last
            message. This is assuming that all previous messages have been checked
            already. Only HumanMessage and AIMessage will be checked. We can perhaps
            loop through and take advantage of the additional_kwargs property in the
            HumanMessage and AIMessage schema to mark messages that have been moderated.
            However that means that this class could generate multiple text chunks
            and moderate() logics would need to be updated. This also means some
            complexity in re-constructing the prompt while keeping the messages in
            sequence.
            """
            message = prompt.messages[-1]
            self.chat_message_index = len(prompt.messages) - 1
            if isinstance(message, HumanMessage):
                input_text = message.content
            if isinstance(message, AIMessage):
                input_text = message.content
        else:
            raise ValueError(
                f"Invalid input type {type(input)}. "
                "Must be a PromptValue, str, or list of BaseMessages."
            )
        return input_text
    def _convert_text_to_prompt(self, prompt: Any, text: str) -> Any:
        if isinstance(prompt, StringPromptValue):
            return StringPromptValue(text=text)
        elif isinstance(prompt, str):
            return text
        elif isinstance(prompt, ChatPromptValue):
            messages = prompt.messages
            message = messages[self.chat_message_index]
            if isinstance(message, HumanMessage):
                messages[self.chat_message_index] = HumanMessage(
                    content=text,
                    example=message.example,
                    additional_kwargs=message.additional_kwargs,
                )
            if isinstance(message, AIMessage):
                messages[self.chat_message_index] = AIMessage(
                    content=text,
                    example=message.example,
                    additional_kwargs=message.additional_kwargs,
                )


example=message.example,
                    additional_kwargs=message.additional_kwargs,
                )
            return ChatPromptValue(messages=messages)
        else:
            raise ValueError(
                f"Invalid input type {type(input)}. "
                "Must be a PromptValue, str, or list of BaseMessages."
            )
    def _moderation_class(self, moderation_class: Any) -> Callable:
        return moderation_class(
            client=self.client,
            callback=self.moderation_callback,
            unique_id=self.unique_id,
            chain_id=self.chain_id,
        ).validate
    def _log_message_for_verbose(self, message: str) -> None:
        if self.run_manager:
            self.run_manager.on_text(message)
[docs]    def moderate(self, prompt: Any) -> str:
        from langchain_experimental.comprehend_moderation.base_moderation_config import (  # noqa: E501
            ModerationIntentConfig,
            ModerationPiiConfig,
            ModerationToxicityConfig,
        )
        from langchain_experimental.comprehend_moderation.base_moderation_exceptions import (  # noqa: E501
            ModerationIntentionError,
            ModerationPiiError,
            ModerationToxicityError,
        )
        try:
            # convert prompt to text
            input_text = self._convert_prompt_to_text(prompt=prompt)
            output_text = str()
            # perform moderation
            filter_functions = {
                "pii": ComprehendPII,
                "toxicity": ComprehendToxicity,
                "intent": ComprehendIntent,
            }
            filters = self.config.filters  # type: ignore
            for _filter in filters:
                filter_name = (
                    "pii"


for _filter in filters:
                filter_name = (
                    "pii"
                    if isinstance(_filter, ModerationPiiConfig)
                    else (
                        "toxicity"
                        if isinstance(_filter, ModerationToxicityConfig)
                        else (
                            "intent"
                            if isinstance(_filter, ModerationIntentConfig)
                            else None
                        )
                    )
                )
                if filter_name in filter_functions:
                    self._log_message_for_verbose(
                        f"Running {filter_name} Validation...\n"
                    )
                    validation_fn = self._moderation_class(
                        moderation_class=filter_functions[filter_name]
                    )
                    input_text = input_text if not output_text else output_text
                    output_text = validation_fn(
                        prompt_value=input_text,
                        config=_filter.dict(),
                    )
            # convert text to prompt and return
            return self._convert_text_to_prompt(prompt=prompt, text=output_text)
        except ModerationPiiError as e:
            self._log_message_for_verbose(f"Found PII content..stopping..\n{str(e)}\n")
            raise e
        except ModerationToxicityError as e:
            self._log_message_for_verbose(
                f"Found Toxic content..stopping..\n{str(e)}\n"
            )
            raise e
        except ModerationIntentionError as e:
            self._log_message_for_verbose(
                f"Found Harmful intention..stopping..\n{str(e)}\n"
            )
            raise e
        except Exception as e:
            raise e


Source code for langchain_experimental.comprehend_moderation.base_moderation_exceptions
[docs]class ModerationPiiError(Exception):
    """Exception raised if PII entities are detected.
    Attributes:
        message -- explanation of the error
    """
    def __init__(
        self, message: str = "The prompt contains PII entities and cannot be processed"
    ):
        self.message = message
        super().__init__(self.message)
[docs]class ModerationToxicityError(Exception):
    """Exception raised if Toxic entities are detected.
    Attributes:
        message -- explanation of the error
    """
    def __init__(
        self, message: str = "The prompt contains toxic content and cannot be processed"
    ):
        self.message = message
        super().__init__(self.message)
[docs]class ModerationIntentionError(Exception):
    """Exception raised if Intention entities are detected.
    Attributes:
        message -- explanation of the error
    """
    def __init__(
        self,
        message: str = (
            "The prompt indicates an un-desired intent and " "cannot be processed"
        ),
    ):
        self.message = message
        super().__init__(self.message)


Source code for langchain_experimental.tabular_synthetic_data.openai
from typing import Any, Dict, Optional, Type, Union
from langchain.chains.openai_functions import create_structured_output_chain
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.pydantic_v1 import BaseModel
from langchain.schema import BaseLLMOutputParser, BasePromptTemplate
from langchain_experimental.tabular_synthetic_data.base import SyntheticDataGenerator
OPENAI_TEMPLATE = PromptTemplate(input_variables=["example"], template="{example}")
[docs]def create_openai_data_generator(
    output_schema: Union[Dict[str, Any], Type[BaseModel]],
    llm: ChatOpenAI,
    prompt: BasePromptTemplate,
    output_parser: Optional[BaseLLMOutputParser] = None,
    **kwargs: Any
) -> SyntheticDataGenerator:
    """
    Create an instance of SyntheticDataGenerator tailored for OpenAI models.
    This function creates an LLM chain designed for structured output based on the
    provided schema, language model, and prompt template. The resulting chain is then
    used to instantiate and return a SyntheticDataGenerator.
    Args:
        output_schema (Union[Dict[str, Any], Type[BaseModel]]): Schema for expected
        output. This can be either a dictionary representing a valid JsonSchema or a
        Pydantic BaseModel class.
        llm (ChatOpenAI): OpenAI language model to use.
        prompt (BasePromptTemplate): Template to be used for generating prompts.
        output_parser (Optional[BaseLLMOutputParser], optional): Parser for
        processing model outputs. If none is provided, a default will be inferred
        from the function types.
        **kwargs: Additional keyword arguments to be passed to
        `create_structured_output_chain`.


`create_structured_output_chain`.
    Returns: SyntheticDataGenerator: An instance of the data generator set up with
    the constructed chain.
    Usage:
        To generate synthetic data with a structured output, first define your desired
        output schema. Then, use this function to create a SyntheticDataGenerator
        instance. After obtaining the generator, you can utilize its methods to produce
        the desired synthetic data.
    """
    # Create function calling chain to ensure structured output
    chain = create_structured_output_chain(
        output_schema, llm, prompt, output_parser=output_parser, **kwargs
    )
    # Create the SyntheticDataGenerator instance with the created chain
    generator = SyntheticDataGenerator(template=prompt, llm_chain=chain)
    return generator


Source code for langchain_experimental.tabular_synthetic_data.base
import asyncio
from typing import Any, Dict, List, Optional, Union
from langchain.chains.base import Chain
from langchain.chains.llm import LLMChain
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.pydantic_v1 import BaseModel, root_validator
from langchain.schema.language_model import BaseLanguageModel
[docs]class SyntheticDataGenerator(BaseModel):
    """Generates synthetic data using the given LLM and few-shot template.
    Utilizes the provided LLM to produce synthetic data based on the
    few-shot prompt template.
    Attributes:
        template (FewShotPromptTemplate): Template for few-shot prompting.
        llm (Optional[BaseLanguageModel]): Large Language Model to use for generation.
        llm_chain (Optional[Chain]): LLM chain with the LLM and few-shot template.
        example_input_key (str): Key to use for storing example inputs.
    Usage Example:
        >>> template = FewShotPromptTemplate(...)
        >>> llm = BaseLanguageModel(...)
        >>> generator = SyntheticDataGenerator(template=template, llm=llm)
        >>> results = generator.generate(subject="climate change", runs=5)
    """
    template: FewShotPromptTemplate
    llm: Optional[BaseLanguageModel] = None
    results: list = []
    llm_chain: Optional[Chain] = None
    example_input_key: str = "example"
    class Config:
        validate_assignment = True
    @root_validator(pre=False, skip_on_failure=True)
    def set_llm_chain(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        llm_chain = values.get("llm_chain")
        llm = values.get("llm")


llm = values.get("llm")
        few_shot_template = values.get("template")
        if not llm_chain:  # If llm_chain is None or not present
            if llm is None or few_shot_template is None:
                raise ValueError(
                    "Both llm and few_shot_template must be provided if llm_chain is "
                    "not given."
                )
            values["llm_chain"] = LLMChain(llm=llm, prompt=few_shot_template)
        return values
    @staticmethod
    def _format_dict_to_string(input_dict: Dict) -> str:
        formatted_str = ", ".join(
            [f"{key}: {value}" for key, value in input_dict.items()]
        )
        return formatted_str
    def _update_examples(self, example: Union[BaseModel, Dict[str, Any], str]) -> None:
        """Prevents duplicates by adding previously generated examples to the few shot
        list."""
        if self.template and self.template.examples:
            if isinstance(example, BaseModel):
                formatted_example = self._format_dict_to_string(example.dict())
            elif isinstance(example, dict):
                formatted_example = self._format_dict_to_string(example)
            else:
                formatted_example = str(example)
            self.template.examples.pop(0)
            self.template.examples.append({self.example_input_key: formatted_example})
[docs]    def generate(self, subject: str, runs: int, *args: Any, **kwargs: Any) -> List[str]:
        """Generate synthetic data using the given subject string.
        Args:
            subject (str): The subject the synthetic data will be about.
            runs (int): Number of times to generate the data.
            extra (str): Extra instructions for steerability in data generation.
        Returns:


extra (str): Extra instructions for steerability in data generation.
        Returns:
            List[str]: List of generated synthetic data.
        Usage Example:
            >>> results = generator.generate(subject="climate change", runs=5,
            extra="Focus on environmental impacts.")
        """
        if self.llm_chain is None:
            raise ValueError(
                "llm_chain is none, either set either llm_chain or llm at generator "
                "construction"
            )
        for _ in range(runs):
            result = self.llm_chain.run(subject=subject, *args, **kwargs)
            self.results.append(result)
            self._update_examples(result)
        return self.results
[docs]    async def agenerate(
        self, subject: str, runs: int, extra: str = "", *args: Any, **kwargs: Any
    ) -> List[str]:
        """Generate synthetic data using the given subject asynchronously.
        Note: Since the LLM calls run concurrently,
        you may have fewer duplicates by adding specific instructions to
        the "extra" keyword argument.
        Args:
            subject (str): The subject the synthetic data will be about.
            runs (int): Number of times to generate the data asynchronously.
            extra (str): Extra instructions for steerability in data generation.
        Returns:
            List[str]: List of generated synthetic data for the given subject.
        Usage Example:
            >>> results = await generator.agenerate(subject="climate change", runs=5,
            extra="Focus on env impacts.")
        """
        async def run_chain(
            subject: str, extra: str = "", *args: Any, **kwargs: Any
        ) -> None:
            if self.llm_chain is not None:


) -> None:
            if self.llm_chain is not None:
                result = await self.llm_chain.arun(
                    subject=subject, extra=extra, *args, **kwargs
                )
                self.results.append(result)
        await asyncio.gather(
            *(run_chain(subject=subject, extra=extra) for _ in range(runs))
        )
        return self.results


Source code for langchain_experimental.plan_and_execute.agent_executor
from typing import Any, Dict, List, Optional
from langchain.callbacks.manager import (
    AsyncCallbackManagerForChainRun,
    CallbackManagerForChainRun,
)
from langchain.chains.base import Chain
from langchain_experimental.plan_and_execute.executors.base import BaseExecutor
from langchain_experimental.plan_and_execute.planners.base import BasePlanner
from langchain_experimental.plan_and_execute.schema import (
    BaseStepContainer,
    ListStepContainer,
)
from langchain_experimental.pydantic_v1 import Field
[docs]class PlanAndExecute(Chain):
    """Plan and execute a chain of steps."""
    planner: BasePlanner
    """The planner to use."""
    executor: BaseExecutor
    """The executor to use."""
    step_container: BaseStepContainer = Field(default_factory=ListStepContainer)
    """The step container to use."""
    input_key: str = "input"
    output_key: str = "output"
    @property
    def input_keys(self) -> List[str]:
        return [self.input_key]
    @property
    def output_keys(self) -> List[str]:
        return [self.output_key]
    def _call(
        self,
        inputs: Dict[str, Any],
        run_manager: Optional[CallbackManagerForChainRun] = None,
    ) -> Dict[str, Any]:
        plan = self.planner.plan(
            inputs,
            callbacks=run_manager.get_child() if run_manager else None,
        )
        if run_manager:
            run_manager.on_text(str(plan), verbose=self.verbose)
        for step in plan.steps:
            _new_inputs = {
                "previous_steps": self.step_container,
                "current_step": step,


"previous_steps": self.step_container,
                "current_step": step,
                "objective": inputs[self.input_key],
            }
            new_inputs = {**_new_inputs, **inputs}
            response = self.executor.step(
                new_inputs,
                callbacks=run_manager.get_child() if run_manager else None,
            )
            if run_manager:
                run_manager.on_text(
                    f"*****\n\nStep: {step.value}", verbose=self.verbose
                )
                run_manager.on_text(
                    f"\n\nResponse: {response.response}", verbose=self.verbose
                )
            self.step_container.add_step(step, response)
        return {self.output_key: self.step_container.get_final_response()}
    async def _acall(
        self,
        inputs: Dict[str, Any],
        run_manager: Optional[AsyncCallbackManagerForChainRun] = None,
    ) -> Dict[str, Any]:
        plan = await self.planner.aplan(
            inputs,
            callbacks=run_manager.get_child() if run_manager else None,
        )
        if run_manager:
            await run_manager.on_text(str(plan), verbose=self.verbose)
        for step in plan.steps:
            _new_inputs = {
                "previous_steps": self.step_container,
                "current_step": step,
                "objective": inputs[self.input_key],
            }
            new_inputs = {**_new_inputs, **inputs}
            response = await self.executor.astep(
                new_inputs,
                callbacks=run_manager.get_child() if run_manager else None,
            )
            if run_manager:
                await run_manager.on_text(
                    f"*****\n\nStep: {step.value}", verbose=self.verbose
                )
                await run_manager.on_text(


)
                await run_manager.on_text(
                    f"\n\nResponse: {response.response}", verbose=self.verbose
                )
            self.step_container.add_step(step, response)
        return {self.output_key: self.step_container.get_final_response()}


Source code for langchain_experimental.plan_and_execute.schema
from abc import abstractmethod
from typing import List, Tuple
from langchain.schema import BaseOutputParser
from langchain_experimental.pydantic_v1 import BaseModel, Field
[docs]class Step(BaseModel):
    """Step."""
    value: str
    """The value."""
[docs]class Plan(BaseModel):
    """Plan."""
    steps: List[Step]
    """The steps."""
[docs]class StepResponse(BaseModel):
    """Step response."""
    response: str
    """The response."""
[docs]class BaseStepContainer(BaseModel):
    """Base step container."""
[docs]    @abstractmethod
    def add_step(self, step: Step, step_response: StepResponse) -> None:
        """Add step and step response to the container."""
[docs]    @abstractmethod
    def get_final_response(self) -> str:
        """Return the final response based on steps taken."""
[docs]class ListStepContainer(BaseStepContainer):
    """List step container."""
    steps: List[Tuple[Step, StepResponse]] = Field(default_factory=list)
    """The steps."""
[docs]    def add_step(self, step: Step, step_response: StepResponse) -> None:
        self.steps.append((step, step_response))
[docs]    def get_steps(self) -> List[Tuple[Step, StepResponse]]:
        return self.steps
[docs]    def get_final_response(self) -> str:
        return self.steps[-1][1].response
[docs]class PlanOutputParser(BaseOutputParser):
    """Plan output parser."""
[docs]    @abstractmethod
    def parse(self, text: str) -> Plan:
        """Parse into a plan."""


Source code for langchain_experimental.plan_and_execute.executors.base
from abc import abstractmethod
from typing import Any
from langchain.callbacks.manager import Callbacks
from langchain.chains.base import Chain
from langchain_experimental.plan_and_execute.schema import StepResponse
from langchain_experimental.pydantic_v1 import BaseModel
[docs]class BaseExecutor(BaseModel):
    """Base executor."""
[docs]    @abstractmethod
    def step(
        self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
    ) -> StepResponse:
        """Take step."""
[docs]    @abstractmethod
    async def astep(
        self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
    ) -> StepResponse:
        """Take async step."""
[docs]class ChainExecutor(BaseExecutor):
    """Chain executor."""
    chain: Chain
    """The chain to use."""
[docs]    def step(
        self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
    ) -> StepResponse:
        """Take step."""
        response = self.chain.run(**inputs, callbacks=callbacks)
        return StepResponse(response=response)
[docs]    async def astep(
        self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
    ) -> StepResponse:
        """Take step."""
        response = await self.chain.arun(**inputs, callbacks=callbacks)
        return StepResponse(response=response)


Source code for langchain_experimental.plan_and_execute.executors.agent_executor
from typing import List
from langchain.agents.agent import AgentExecutor
from langchain.agents.structured_chat.base import StructuredChatAgent
from langchain.schema.language_model import BaseLanguageModel
from langchain.tools import BaseTool
from langchain_experimental.plan_and_execute.executors.base import ChainExecutor
HUMAN_MESSAGE_TEMPLATE = """Previous steps: {previous_steps}
Current objective: {current_step}
{agent_scratchpad}"""
TASK_PREFIX = """{objective}
"""
[docs]def load_agent_executor(
    llm: BaseLanguageModel,
    tools: List[BaseTool],
    verbose: bool = False,
    include_task_in_prompt: bool = False,
) -> ChainExecutor:
    """
    Load an agent executor.
    Args:
        llm: BaseLanguageModel
        tools: List[BaseTool]
        verbose: bool. Defaults to False.
        include_task_in_prompt: bool. Defaults to False.
    Returns:
        ChainExecutor
    """
    input_variables = ["previous_steps", "current_step", "agent_scratchpad"]
    template = HUMAN_MESSAGE_TEMPLATE
    if include_task_in_prompt:
        input_variables.append("objective")
        template = TASK_PREFIX + template
    agent = StructuredChatAgent.from_llm_and_tools(
        llm,
        tools,
        human_message_template=template,
        input_variables=input_variables,
    )
    agent_executor = AgentExecutor.from_agent_and_tools(
        agent=agent, tools=tools, verbose=verbose
    )
    return ChainExecutor(chain=agent_executor)


Source code for langchain_experimental.plan_and_execute.planners.chat_planner
import re
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.schema.language_model import BaseLanguageModel
from langchain.schema.messages import SystemMessage
from langchain_experimental.plan_and_execute.planners.base import LLMPlanner
from langchain_experimental.plan_and_execute.schema import (
    Plan,
    PlanOutputParser,
    Step,
)
SYSTEM_PROMPT = (
    "Let's first understand the problem and devise a plan to solve the problem."
    " Please output the plan starting with the header 'Plan:' "
    "and then followed by a numbered list of steps. "
    "Please make the plan the minimum number of steps required "
    "to accurately complete the task. If the task is a question, "
    "the final step should almost always be 'Given the above steps taken, "
    "please respond to the users original question'. "
    "At the end of your plan, say '<END_OF_PLAN>'"
)
[docs]class PlanningOutputParser(PlanOutputParser):
    """Planning output parser."""
[docs]    def parse(self, text: str) -> Plan:
        steps = [Step(value=v) for v in re.split("\n\s*\d+\. ", text)[1:]]
        return Plan(steps=steps)
[docs]def load_chat_planner(
    llm: BaseLanguageModel, system_prompt: str = SYSTEM_PROMPT
) -> LLMPlanner:
    """
    Load a chat planner.
    Args:
        llm: Language model.
        system_prompt: System prompt.
    Returns:
        LLMPlanner
    """


Returns:
        LLMPlanner
    """
    prompt_template = ChatPromptTemplate.from_messages(
        [
            SystemMessage(content=system_prompt),
            HumanMessagePromptTemplate.from_template("{input}"),
        ]
    )
    llm_chain = LLMChain(llm=llm, prompt=prompt_template)
    return LLMPlanner(
        llm_chain=llm_chain,
        output_parser=PlanningOutputParser(),
        stop=["<END_OF_PLAN>"],
    )


Source code for langchain_experimental.plan_and_execute.planners.base
from abc import abstractmethod
from typing import Any, List, Optional
from langchain.callbacks.manager import Callbacks
from langchain.chains.llm import LLMChain
from langchain_experimental.plan_and_execute.schema import Plan, PlanOutputParser
from langchain_experimental.pydantic_v1 import BaseModel
[docs]class BasePlanner(BaseModel):
    """Base planner."""
[docs]    @abstractmethod
    def plan(self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any) -> Plan:
        """Given input, decide what to do."""
[docs]    @abstractmethod
    async def aplan(
        self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
    ) -> Plan:
        """Given input, asynchronously decide what to do."""
[docs]class LLMPlanner(BasePlanner):
    """LLM planner."""
    llm_chain: LLMChain
    """The LLM chain to use."""
    output_parser: PlanOutputParser
    """The output parser to use."""
    stop: Optional[List] = None
    """The stop list to use."""
[docs]    def plan(self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any) -> Plan:
        """Given input, decide what to do."""
        llm_response = self.llm_chain.run(**inputs, stop=self.stop, callbacks=callbacks)
        return self.output_parser.parse(llm_response)
[docs]    async def aplan(
        self, inputs: dict, callbacks: Callbacks = None, **kwargs: Any
    ) -> Plan:
        """Given input, asynchronously decide what to do."""
        llm_response = await self.llm_chain.arun(


llm_response = await self.llm_chain.arun(
            **inputs, stop=self.stop, callbacks=callbacks
        )
        return self.output_parser.parse(llm_response)


Source code for langchain.cache
"""
.. warning::
  Beta Feature!
**Cache** provides an optional caching layer for LLMs.
Cache is useful for two reasons:
- It can save you money by reducing the number of API calls you make to the LLM
  provider if you're often requesting the same completion multiple times.
- It can speed up your application by reducing the number of API calls you make
  to the LLM provider.
Cache directly competes with Memory. See documentation for Pros and Cons.
**Class hierarchy:**
.. code-block::
    BaseCache --> <name>Cache  # Examples: InMemoryCache, RedisCache, GPTCache
"""
from __future__ import annotations
import hashlib
import inspect
import json
import logging
import warnings
from datetime import timedelta
from functools import lru_cache
from typing import (
    TYPE_CHECKING,
    Any,
    Callable,
    Dict,
    List,
    Optional,
    Tuple,
    Type,
    Union,
    cast,
)
from sqlalchemy import Column, Integer, String, create_engine, select
from sqlalchemy.engine.base import Engine
from sqlalchemy.orm import Session
try:
    from sqlalchemy.orm import declarative_base
except ImportError:
    from sqlalchemy.ext.declarative import declarative_base
from langchain.llms.base import LLM, get_prompts
from langchain.load.dump import dumps
from langchain.load.load import loads
from langchain.schema import ChatGeneration, Generation
from langchain.schema.cache import RETURN_VAL_TYPE, BaseCache
from langchain.schema.embeddings import Embeddings
from langchain.utils import get_from_env
from langchain.vectorstores.redis import Redis as RedisVectorstore
logger = logging.getLogger(__file__)
if TYPE_CHECKING:
    import momento
    from cassandra.cluster import Session as CassandraSession


import momento
    from cassandra.cluster import Session as CassandraSession
def _hash(_input: str) -> str:
    """Use a deterministic hashing approach."""
    return hashlib.md5(_input.encode()).hexdigest()
def _dump_generations_to_json(generations: RETURN_VAL_TYPE) -> str:
    """Dump generations to json.
    Args:
        generations (RETURN_VAL_TYPE): A list of language model generations.
    Returns:
        str: Json representing a list of generations.
    Warning: would not work well with arbitrary subclasses of `Generation`
    """
    return json.dumps([generation.dict() for generation in generations])
def _load_generations_from_json(generations_json: str) -> RETURN_VAL_TYPE:
    """Load generations from json.
    Args:
        generations_json (str): A string of json representing a list of generations.
    Raises:
        ValueError: Could not decode json string to list of generations.
    Returns:
        RETURN_VAL_TYPE: A list of generations.
    Warning: would not work well with arbitrary subclasses of `Generation`
    """
    try:
        results = json.loads(generations_json)
        return [Generation(**generation_dict) for generation_dict in results]
    except json.JSONDecodeError:
        raise ValueError(
            f"Could not decode json to list of generations: {generations_json}"
        )
def _dumps_generations(generations: RETURN_VAL_TYPE) -> str:
    """
    Serialization for generic RETURN_VAL_TYPE, i.e. sequence of `Generation`
    Args:
        generations (RETURN_VAL_TYPE): A list of language model generations.
    Returns:
        str: a single string representing a list of generations.
    This function (+ its counterpart `_loads_generations`) rely on


This function (+ its counterpart `_loads_generations`) rely on
    the dumps/loads pair with Reviver, so are able to deal
    with all subclasses of Generation.
    Each item in the list can be `dumps`ed to a string,
    then we make the whole list of strings into a json-dumped.
    """
    return json.dumps([dumps(_item) for _item in generations])
def _loads_generations(generations_str: str) -> Union[RETURN_VAL_TYPE, None]:
    """
    Deserialization of a string into a generic RETURN_VAL_TYPE
    (i.e. a sequence of `Generation`).
    See `_dumps_generations`, the inverse of this function.
    Args:
        generations_str (str): A string representing a list of generations.
    Compatible with the legacy cache-blob format
    Does not raise exceptions for malformed entries, just logs a warning
    and returns none: the caller should be prepared for such a cache miss.
    Returns:
        RETURN_VAL_TYPE: A list of generations.
    """
    try:
        generations = [loads(_item_str) for _item_str in json.loads(generations_str)]
        return generations
    except (json.JSONDecodeError, TypeError):
        # deferring the (soft) handling to after the legacy-format attempt
        pass
    try:
        gen_dicts = json.loads(generations_str)
        # not relying on `_load_generations_from_json` (which could disappear):
        generations = [Generation(**generation_dict) for generation_dict in gen_dicts]
        logger.warning(
            f"Legacy 'Generation' cached blob encountered: '{generations_str}'"
        )
        return generations
    except (json.JSONDecodeError, TypeError):
        logger.warning(


return generations
    except (json.JSONDecodeError, TypeError):
        logger.warning(
            f"Malformed/unparsable cached blob encountered: '{generations_str}'"
        )
        return None
[docs]class InMemoryCache(BaseCache):
    """Cache that stores things in memory."""
[docs]    def __init__(self) -> None:
        """Initialize with empty cache."""
        self._cache: Dict[Tuple[str, str], RETURN_VAL_TYPE] = {}
[docs]    def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]:
        """Look up based on prompt and llm_string."""
        return self._cache.get((prompt, llm_string), None)
[docs]    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update cache based on prompt and llm_string."""
        self._cache[(prompt, llm_string)] = return_val
[docs]    def clear(self, **kwargs: Any) -> None:
        """Clear cache."""
        self._cache = {}
Base = declarative_base()
[docs]class FullLLMCache(Base):  # type: ignore
    """SQLite table for full LLM Cache (all generations)."""
    __tablename__ = "full_llm_cache"
    prompt = Column(String, primary_key=True)
    llm = Column(String, primary_key=True)
    idx = Column(Integer, primary_key=True)
    response = Column(String)
[docs]class SQLAlchemyCache(BaseCache):
    """Cache that uses SQAlchemy as a backend."""
[docs]    def __init__(self, engine: Engine, cache_schema: Type[FullLLMCache] = FullLLMCache):


"""Initialize by creating all tables."""
        self.engine = engine
        self.cache_schema = cache_schema
        self.cache_schema.metadata.create_all(self.engine)
[docs]    def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]:
        """Look up based on prompt and llm_string."""
        stmt = (
            select(self.cache_schema.response)
            .where(self.cache_schema.prompt == prompt)  # type: ignore
            .where(self.cache_schema.llm == llm_string)
            .order_by(self.cache_schema.idx)
        )
        with Session(self.engine) as session:
            rows = session.execute(stmt).fetchall()
            if rows:
                try:
                    return [loads(row[0]) for row in rows]
                except Exception:
                    logger.warning(
                        "Retrieving a cache value that could not be deserialized "
                        "properly. This is likely due to the cache being in an "
                        "older format. Please recreate your cache to avoid this "
                        "error."
                    )
                    # In a previous life we stored the raw text directly
                    # in the table, so assume it's in that format.
                    return [Generation(text=row[0]) for row in rows]
        return None
[docs]    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update based on prompt and llm_string."""
        items = [
            self.cache_schema(prompt=prompt, llm=llm_string, response=dumps(gen), idx=i)
            for i, gen in enumerate(return_val)
        ]
        with Session(self.engine) as session, session.begin():
            for item in items:
                session.merge(item)


for item in items:
                session.merge(item)
[docs]    def clear(self, **kwargs: Any) -> None:
        """Clear cache."""
        with Session(self.engine) as session:
            session.query(self.cache_schema).delete()
            session.commit()
[docs]class SQLiteCache(SQLAlchemyCache):
    """Cache that uses SQLite as a backend."""
[docs]    def __init__(self, database_path: str = ".langchain.db"):
        """Initialize by creating the engine and all tables."""
        engine = create_engine(f"sqlite:///{database_path}")
        super().__init__(engine)
[docs]class RedisCache(BaseCache):
    """Cache that uses Redis as a backend."""
[docs]    def __init__(self, redis_: Any, *, ttl: Optional[int] = None):
        """
        Initialize an instance of RedisCache.
        This method initializes an object with Redis caching capabilities.
        It takes a `redis_` parameter, which should be an instance of a Redis
        client class, allowing the object to interact with a Redis
        server for caching purposes.
        Parameters:
            redis_ (Any): An instance of a Redis client class
                (e.g., redis.Redis) used for caching.
                This allows the object to communicate with a
                Redis server for caching operations.
            ttl (int, optional): Time-to-live (TTL) for cached items in seconds.
                If provided, it sets the time duration for how long cached
                items will remain valid. If not provided, cached items will not
                have an automatic expiration.
        """
        try:
            from redis import Redis
        except ImportError:
            raise ValueError(
                "Could not import redis python package. "
                "Please install it with `pip install redis`."


"Please install it with `pip install redis`."
            )
        if not isinstance(redis_, Redis):
            raise ValueError("Please pass in Redis object.")
        self.redis = redis_
        self.ttl = ttl
    def _key(self, prompt: str, llm_string: str) -> str:
        """Compute key from prompt and llm_string"""
        return _hash(prompt + llm_string)
[docs]    def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]:
        """Look up based on prompt and llm_string."""
        generations = []
        # Read from a Redis HASH
        results = self.redis.hgetall(self._key(prompt, llm_string))
        if results:
            for _, text in results.items():
                generations.append(Generation(text=text))
        return generations if generations else None
[docs]    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update cache based on prompt and llm_string."""
        for gen in return_val:
            if not isinstance(gen, Generation):
                raise ValueError(
                    "RedisCache only supports caching of normal LLM generations, "
                    f"got {type(gen)}"
                )
            if isinstance(gen, ChatGeneration):
                warnings.warn(
                    "NOTE: Generation has not been cached. RedisCache does not"
                    " support caching ChatModel outputs."
                )
                return
        # Write to a Redis HASH
        key = self._key(prompt, llm_string)
        with self.redis.pipeline() as pipe:
            pipe.hset(
                key,
                mapping={
                    str(idx): generation.text
                    for idx, generation in enumerate(return_val)
                },
            )


for idx, generation in enumerate(return_val)
                },
            )
            if self.ttl is not None:
                pipe.expire(key, self.ttl)
            pipe.execute()
[docs]    def clear(self, **kwargs: Any) -> None:
        """Clear cache. If `asynchronous` is True, flush asynchronously."""
        asynchronous = kwargs.get("asynchronous", False)
        self.redis.flushdb(asynchronous=asynchronous, **kwargs)
[docs]class RedisSemanticCache(BaseCache):
    """Cache that uses Redis as a vector-store backend."""
    # TODO - implement a TTL policy in Redis
    DEFAULT_SCHEMA = {
        "content_key": "prompt",
        "text": [
            {"name": "prompt"},
        ],
        "extra": [{"name": "return_val"}, {"name": "llm_string"}],
    }
[docs]    def __init__(
        self, redis_url: str, embedding: Embeddings, score_threshold: float = 0.2
    ):
        """Initialize by passing in the `init` GPTCache func
        Args:
            redis_url (str): URL to connect to Redis.
            embedding (Embedding): Embedding provider for semantic encoding and search.
            score_threshold (float, 0.2):
        Example:
        .. code-block:: python
            import langchain
            from langchain.cache import RedisSemanticCache
            from langchain.embeddings import OpenAIEmbeddings
            langchain.llm_cache = RedisSemanticCache(
                redis_url="redis://localhost:6379",
                embedding=OpenAIEmbeddings()
            )
        """
        self._cache_dict: Dict[str, RedisVectorstore] = {}
        self.redis_url = redis_url
        self.embedding = embedding


self.redis_url = redis_url
        self.embedding = embedding
        self.score_threshold = score_threshold
    def _index_name(self, llm_string: str) -> str:
        hashed_index = _hash(llm_string)
        return f"cache:{hashed_index}"
    def _get_llm_cache(self, llm_string: str) -> RedisVectorstore:
        index_name = self._index_name(llm_string)
        # return vectorstore client for the specific llm string
        if index_name in self._cache_dict:
            return self._cache_dict[index_name]
        # create new vectorstore client for the specific llm string
        try:
            self._cache_dict[index_name] = RedisVectorstore.from_existing_index(
                embedding=self.embedding,
                index_name=index_name,
                redis_url=self.redis_url,
                schema=cast(Dict, self.DEFAULT_SCHEMA),
            )
        except ValueError:
            redis = RedisVectorstore(
                embedding=self.embedding,
                index_name=index_name,
                redis_url=self.redis_url,
                index_schema=cast(Dict, self.DEFAULT_SCHEMA),
            )
            _embedding = self.embedding.embed_query(text="test")
            redis._create_index(dim=len(_embedding))
            self._cache_dict[index_name] = redis
        return self._cache_dict[index_name]
[docs]    def clear(self, **kwargs: Any) -> None:
        """Clear semantic cache for a given llm_string."""
        index_name = self._index_name(kwargs["llm_string"])
        if index_name in self._cache_dict:
            self._cache_dict[index_name].drop_index(
                index_name=index_name, delete_documents=True, redis_url=self.redis_url
            )
            del self._cache_dict[index_name]


)
            del self._cache_dict[index_name]
[docs]    def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]:
        """Look up based on prompt and llm_string."""
        llm_cache = self._get_llm_cache(llm_string)
        generations: List = []
        # Read from a Hash
        results = llm_cache.similarity_search(
            query=prompt,
            k=1,
            distance_threshold=self.score_threshold,
        )
        if results:
            for document in results:
                generations.extend(
                    _load_generations_from_json(document.metadata["return_val"])
                )
        return generations if generations else None
[docs]    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update cache based on prompt and llm_string."""
        for gen in return_val:
            if not isinstance(gen, Generation):
                raise ValueError(
                    "RedisSemanticCache only supports caching of "
                    f"normal LLM generations, got {type(gen)}"
                )
            if isinstance(gen, ChatGeneration):
                warnings.warn(
                    "NOTE: Generation has not been cached. RedisSentimentCache does not"
                    " support caching ChatModel outputs."
                )
                return
        llm_cache = self._get_llm_cache(llm_string)
        _dump_generations_to_json([g for g in return_val])
        metadata = {
            "llm_string": llm_string,
            "prompt": prompt,
            "return_val": _dump_generations_to_json([g for g in return_val]),
        }
        llm_cache.add_texts(texts=[prompt], metadatas=[metadata])


llm_cache.add_texts(texts=[prompt], metadatas=[metadata])
[docs]class GPTCache(BaseCache):
    """Cache that uses GPTCache as a backend."""
[docs]    def __init__(
        self,
        init_func: Union[
            Callable[[Any, str], None], Callable[[Any], None], None
        ] = None,
    ):
        """Initialize by passing in init function (default: `None`).
        Args:
            init_func (Optional[Callable[[Any], None]]): init `GPTCache` function
            (default: `None`)
        Example:
        .. code-block:: python
            # Initialize GPTCache with a custom init function
            import gptcache
            from gptcache.processor.pre import get_prompt
            from gptcache.manager.factory import get_data_manager
            # Avoid multiple caches using the same file,
            causing different llm model caches to affect each other
            def init_gptcache(cache_obj: gptcache.Cache, llm str):
                cache_obj.init(
                    pre_embedding_func=get_prompt,
                    data_manager=manager_factory(
                        manager="map",
                        data_dir=f"map_cache_{llm}"
                    ),
                )
            langchain.llm_cache = GPTCache(init_gptcache)
        """
        try:
            import gptcache  # noqa: F401
        except ImportError:
            raise ImportError(
                "Could not import gptcache python package. "
                "Please install it with `pip install gptcache`."
            )
        self.init_gptcache_func: Union[
            Callable[[Any, str], None], Callable[[Any], None], None
        ] = init_func


] = init_func
        self.gptcache_dict: Dict[str, Any] = {}
    def _new_gptcache(self, llm_string: str) -> Any:
        """New gptcache object"""
        from gptcache import Cache
        from gptcache.manager.factory import get_data_manager
        from gptcache.processor.pre import get_prompt
        _gptcache = Cache()
        if self.init_gptcache_func is not None:
            sig = inspect.signature(self.init_gptcache_func)
            if len(sig.parameters) == 2:
                self.init_gptcache_func(_gptcache, llm_string)  # type: ignore[call-arg]
            else:
                self.init_gptcache_func(_gptcache)  # type: ignore[call-arg]
        else:
            _gptcache.init(
                pre_embedding_func=get_prompt,
                data_manager=get_data_manager(data_path=llm_string),
            )
        self.gptcache_dict[llm_string] = _gptcache
        return _gptcache
    def _get_gptcache(self, llm_string: str) -> Any:
        """Get a cache object.
        When the corresponding llm model cache does not exist, it will be created."""
        _gptcache = self.gptcache_dict.get(llm_string, None)
        if not _gptcache:
            _gptcache = self._new_gptcache(llm_string)
        return _gptcache
[docs]    def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]:
        """Look up the cache data.
        First, retrieve the corresponding cache object using the `llm_string` parameter,


First, retrieve the corresponding cache object using the `llm_string` parameter,
        and then retrieve the data from the cache based on the `prompt`.
        """
        from gptcache.adapter.api import get
        _gptcache = self._get_gptcache(llm_string)
        res = get(prompt, cache_obj=_gptcache)
        if res:
            return [
                Generation(**generation_dict) for generation_dict in json.loads(res)
            ]
        return None
[docs]    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update cache.
        First, retrieve the corresponding cache object using the `llm_string` parameter,
        and then store the `prompt` and `return_val` in the cache object.
        """
        for gen in return_val:
            if not isinstance(gen, Generation):
                raise ValueError(
                    "GPTCache only supports caching of normal LLM generations, "
                    f"got {type(gen)}"
                )
        from gptcache.adapter.api import put
        _gptcache = self._get_gptcache(llm_string)
        handled_data = json.dumps([generation.dict() for generation in return_val])
        put(prompt, handled_data, cache_obj=_gptcache)
        return None
[docs]    def clear(self, **kwargs: Any) -> None:
        """Clear cache."""
        from gptcache import Cache
        for gptcache_instance in self.gptcache_dict.values():
            gptcache_instance = cast(Cache, gptcache_instance)
            gptcache_instance.flush()
        self.gptcache_dict.clear()
def _ensure_cache_exists(cache_client: momento.CacheClient, cache_name: str) -> None:


"""Create cache if it doesn't exist.
    Raises:
        SdkException: Momento service or network error
        Exception: Unexpected response
    """
    from momento.responses import CreateCache
    create_cache_response = cache_client.create_cache(cache_name)
    if isinstance(create_cache_response, CreateCache.Success) or isinstance(
        create_cache_response, CreateCache.CacheAlreadyExists
    ):
        return None
    elif isinstance(create_cache_response, CreateCache.Error):
        raise create_cache_response.inner_exception
    else:
        raise Exception(f"Unexpected response cache creation: {create_cache_response}")
def _validate_ttl(ttl: Optional[timedelta]) -> None:
    if ttl is not None and ttl <= timedelta(seconds=0):
        raise ValueError(f"ttl must be positive but was {ttl}.")
[docs]class MomentoCache(BaseCache):
    """Cache that uses Momento as a backend. See https://gomomento.com/"""
[docs]    def __init__(
        self,
        cache_client: momento.CacheClient,
        cache_name: str,
        *,
        ttl: Optional[timedelta] = None,
        ensure_cache_exists: bool = True,
    ):
        """Instantiate a prompt cache using Momento as a backend.
        Note: to instantiate the cache client passed to MomentoCache,
        you must have a Momento account. See https://gomomento.com/.
        Args:
            cache_client (CacheClient): The Momento cache client.
            cache_name (str): The name of the cache to use to store the data.
            ttl (Optional[timedelta], optional): The time to live for the cache items.
                Defaults to None, ie use the client default TTL.
            ensure_cache_exists (bool, optional): Create the cache if it doesn't


ensure_cache_exists (bool, optional): Create the cache if it doesn't
                exist. Defaults to True.
        Raises:
            ImportError: Momento python package is not installed.
            TypeError: cache_client is not of type momento.CacheClientObject
            ValueError: ttl is non-null and non-negative
        """
        try:
            from momento import CacheClient
        except ImportError:
            raise ImportError(
                "Could not import momento python package. "
                "Please install it with `pip install momento`."
            )
        if not isinstance(cache_client, CacheClient):
            raise TypeError("cache_client must be a momento.CacheClient object.")
        _validate_ttl(ttl)
        if ensure_cache_exists:
            _ensure_cache_exists(cache_client, cache_name)
        self.cache_client = cache_client
        self.cache_name = cache_name
        self.ttl = ttl
[docs]    @classmethod
    def from_client_params(
        cls,
        cache_name: str,
        ttl: timedelta,
        *,
        configuration: Optional[momento.config.Configuration] = None,
        auth_token: Optional[str] = None,
        **kwargs: Any,
    ) -> MomentoCache:
        """Construct cache from CacheClient parameters."""
        try:
            from momento import CacheClient, Configurations, CredentialProvider
        except ImportError:
            raise ImportError(
                "Could not import momento python package. "
                "Please install it with `pip install momento`."
            )
        if configuration is None:
            configuration = Configurations.Laptop.v1()
        auth_token = auth_token or get_from_env("auth_token", "MOMENTO_AUTH_TOKEN")
        credentials = CredentialProvider.from_string(auth_token)
        cache_client = CacheClient(configuration, credentials, default_ttl=ttl)


cache_client = CacheClient(configuration, credentials, default_ttl=ttl)
        return cls(cache_client, cache_name, ttl=ttl, **kwargs)
    def __key(self, prompt: str, llm_string: str) -> str:
        """Compute cache key from prompt and associated model and settings.
        Args:
            prompt (str): The prompt run through the language model.
            llm_string (str): The language model version and settings.
        Returns:
            str: The cache key.
        """
        return _hash(prompt + llm_string)
[docs]    def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]:
        """Lookup llm generations in cache by prompt and associated model and settings.
        Args:
            prompt (str): The prompt run through the language model.
            llm_string (str): The language model version and settings.
        Raises:
            SdkException: Momento service or network error
        Returns:
            Optional[RETURN_VAL_TYPE]: A list of language model generations.
        """
        from momento.responses import CacheGet
        generations: RETURN_VAL_TYPE = []
        get_response = self.cache_client.get(
            self.cache_name, self.__key(prompt, llm_string)
        )
        if isinstance(get_response, CacheGet.Hit):
            value = get_response.value_string
            generations = _load_generations_from_json(value)
        elif isinstance(get_response, CacheGet.Miss):
            pass
        elif isinstance(get_response, CacheGet.Error):
            raise get_response.inner_exception
        return generations if generations else None
[docs]    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Store llm generations in cache.
        Args:


"""Store llm generations in cache.
        Args:
            prompt (str): The prompt run through the language model.
            llm_string (str): The language model string.
            return_val (RETURN_VAL_TYPE): A list of language model generations.
        Raises:
            SdkException: Momento service or network error
            Exception: Unexpected response
        """
        for gen in return_val:
            if not isinstance(gen, Generation):
                raise ValueError(
                    "Momento only supports caching of normal LLM generations, "
                    f"got {type(gen)}"
                )
        key = self.__key(prompt, llm_string)
        value = _dump_generations_to_json(return_val)
        set_response = self.cache_client.set(self.cache_name, key, value, self.ttl)
        from momento.responses import CacheSet
        if isinstance(set_response, CacheSet.Success):
            pass
        elif isinstance(set_response, CacheSet.Error):
            raise set_response.inner_exception
        else:
            raise Exception(f"Unexpected response: {set_response}")
[docs]    def clear(self, **kwargs: Any) -> None:
        """Clear the cache.
        Raises:
            SdkException: Momento service or network error
        """
        from momento.responses import CacheFlush
        flush_response = self.cache_client.flush_cache(self.cache_name)
        if isinstance(flush_response, CacheFlush.Success):
            pass
        elif isinstance(flush_response, CacheFlush.Error):
            raise flush_response.inner_exception
CASSANDRA_CACHE_DEFAULT_TABLE_NAME = "langchain_llm_cache"
CASSANDRA_CACHE_DEFAULT_TTL_SECONDS = None
[docs]class CassandraCache(BaseCache):
    """
    Cache that uses Cassandra / Astra DB as a backend.
    It uses a single Cassandra table.


It uses a single Cassandra table.
    The lookup keys (which get to form the primary key) are:
        - prompt, a string
        - llm_string, a deterministic str representation of the model parameters.
          (needed to prevent collisions same-prompt-different-model collisions)
    """
[docs]    def __init__(
        self,
        session: Optional[CassandraSession] = None,
        keyspace: Optional[str] = None,
        table_name: str = CASSANDRA_CACHE_DEFAULT_TABLE_NAME,
        ttl_seconds: Optional[int] = CASSANDRA_CACHE_DEFAULT_TTL_SECONDS,
        skip_provisioning: bool = False,
    ):
        """
        Initialize with a ready session and a keyspace name.
        Args:
            session (cassandra.cluster.Session): an open Cassandra session
            keyspace (str): the keyspace to use for storing the cache
            table_name (str): name of the Cassandra table to use as cache
            ttl_seconds (optional int): time-to-live for cache entries
                (default: None, i.e. forever)
        """
        try:
            from cassio.table import ElasticCassandraTable
        except (ImportError, ModuleNotFoundError):
            raise ValueError(
                "Could not import cassio python package. "
                "Please install it with `pip install cassio`."
            )
        self.session = session
        self.keyspace = keyspace
        self.table_name = table_name
        self.ttl_seconds = ttl_seconds
        self.kv_cache = ElasticCassandraTable(
            session=self.session,
            keyspace=self.keyspace,
            table=self.table_name,
            keys=["llm_string", "prompt"],
            primary_key_type=["TEXT", "TEXT"],
            ttl_seconds=self.ttl_seconds,


primary_key_type=["TEXT", "TEXT"],
            ttl_seconds=self.ttl_seconds,
            skip_provisioning=skip_provisioning,
        )
[docs]    def lookup(self, prompt: str, llm_string: str) -> Optional[RETURN_VAL_TYPE]:
        """Look up based on prompt and llm_string."""
        item = self.kv_cache.get(
            llm_string=_hash(llm_string),
            prompt=_hash(prompt),
        )
        if item is not None:
            generations = _loads_generations(item["body_blob"])
            # this protects against malformed cached items:
            if generations is not None:
                return generations
            else:
                return None
        else:
            return None
[docs]    def update(self, prompt: str, llm_string: str, return_val: RETURN_VAL_TYPE) -> None:
        """Update cache based on prompt and llm_string."""
        blob = _dumps_generations(return_val)
        self.kv_cache.put(
            llm_string=_hash(llm_string),
            prompt=_hash(prompt),
            body_blob=blob,
        )
[docs]    def delete_through_llm(
        self, prompt: str, llm: LLM, stop: Optional[List[str]] = None
    ) -> None:
        """
        A wrapper around `delete` with the LLM being passed.
        In case the llm(prompt) calls have a `stop` param, you should pass it here
        """
        llm_string = get_prompts(
            {**llm.dict(), **{"stop": stop}},
            [],
        )[1]
        return self.delete(prompt, llm_string=llm_string)