U
    DAf;'                     @   sZ   d dl mZmZmZmZmZ d dlmZ d dlm	Z	 d dl
mZmZ eG dd dZdS )    )AnyDictListOptionalSet)meta)SandboxedEnvironment)	componentdefault_to_dictc                   @   s   e Zd ZdZdeeee  eee  dddZeee	f dddZ
ejed	dee eeee	f  d
ddZee dddZdS )PromptBuildera  
    PromptBuilder is a component that renders a prompt from a template string using Jinja2 templates.

    For prompt engineering, users can switch the template at runtime by providing a template for each pipeline run
    invocation.

    The template variables found in the default template string are used as input types for the component and are all
    optional, unless explicitly specified. If an optional template variable is not provided as an input, it will be
    replaced with an empty string in the rendered prompt. Use `variables` and `required_variables` to change the
    default variable behavior.

    ### Usage examples

    #### On its own

    Below is an example of using the `PromptBuilder` to render a prompt template and fill it with `target_language`
    and `snippet`. The PromptBuilder returns a prompt with the string "Translate the following context to spanish.
    Context: I can't speak spanish.; Translation:".
    ```python
    from haystack.components.builders import PromptBuilder

    template = "Translate the following context to {{ target_language }}. Context: {{ snippet }}; Translation:"
    builder = PromptBuilder(template=template)
    builder.run(target_language="spanish", snippet="I can't speak spanish.")
    ```

    #### In a Pipeline

    Below is an example of a RAG pipeline where we use a `PromptBuilder` to render a custom prompt template and fill it
    with the contents of retrieved Documents and a query. The rendered prompt is then sent to a Generator.
    ```python
    from haystack import Pipeline, Document
    from haystack.utils import Secret
    from haystack.components.generators import OpenAIGenerator
    from haystack.components.builders.prompt_builder import PromptBuilder

    # in a real world use case documents could come from a retriever, web, or any other source
    documents = [Document(content="Joe lives in Berlin"), Document(content="Joe is a software engineer")]
    prompt_template = """
        Given these documents, answer the question.
        Documents:
        {% for doc in documents %}
            {{ doc.content }}
        {% endfor %}

        Question: {{query}}
        Answer:
        """
    p = Pipeline()
    p.add_component(instance=PromptBuilder(template=prompt_template), name="prompt_builder")
    p.add_component(instance=OpenAIGenerator(api_key=Secret.from_env_var("OPENAI_API_KEY")), name="llm")
    p.connect("prompt_builder", "llm")

    question = "Where does Joe live?"
    result = p.run({"prompt_builder": {"documents": documents, "query": question}})
    print(result)
    ```

    #### Changing the template at runtime (Prompt Engineering)

    `PromptBuilder` allows you to switch the prompt template of an existing pipeline.
    Below's example builds on top of the existing pipeline of the previous section.
    The existing pipeline is invoked with a new prompt template:
    ```python
    documents = [
        Document(content="Joe lives in Berlin", meta={"name": "doc1"}),
        Document(content="Joe is a software engineer", meta={"name": "doc1"}),
    ]
    new_template = """
        You are a helpful assistant.
        Given these documents, answer the question.
        Documents:
        {% for doc in documents %}
            Document {{ loop.index }}:
            Document name: {{ doc.meta['name'] }}
            {{ doc.content }}
        {% endfor %}

        Question: {{ query }}
        Answer:
        """
    p.run({
        "prompt_builder": {
            "documents": documents,
            "query": question,
            "template": new_template,
        },
    })
    ```
    If you want to use different variables during prompt engineering than in the default template,
    you can do so by setting `PromptBuilder`'s `variables` init parameter accordingly.

    #### Overwriting variables at runtime

    In case you want to overwrite the values of variables, you can use `template_variables` during runtime as
    illustrated below:
    ```python
    language_template = """
    You are a helpful assistant.
    Given these documents, answer the question.
    Documents:
    {% for doc in documents %}
        Document {{ loop.index }}:
        Document name: {{ doc.meta['name'] }}
        {{ doc.content }}
    {% endfor %}

    Question: {{ query }}
    Please provide your answer in {{ answer_language | default('English') }}
    Answer:
    """
    p.run({
        "prompt_builder": {
            "documents": documents,
            "query": question,
            "template": language_template,
            "template_variables": {"answer_language": "German"},
        },
    })
    ```
    Note that `language_template` introduces variable `answer_language` which is not bound to any pipeline variable.
    If not set otherwise, it would evaluate to its default value 'English'.
    In this example we are overwriting its value to 'German'.
    `template_variables` allows you to overwrite pipeline variables (such as documents) as well.

    N)templaterequired_variables	variablesc                 C   s   || _ || _|| _|pg | _t | _| j|| _|sT| j|}t	
|}t|}|pZg }tt ttttf  d}tj| f| |D ].}|| jkrt| |t qt| |td qdS )a  
        Constructs a PromptBuilder component.

        :param template:
            A Jinja2 template string that is used to render the prompt, e.g.:
            `"Summarize this document: {{ documents[0].content }}\nSummary:"`
        :param required_variables: An optional list of input variables that must be provided at runtime.
            If a required variable is not provided at runtime, an exception will be raised.
        :param variables:
            An optional list of input variables to be used in prompt templates instead of the ones inferred from
            `template`. For example, if you want to use more variables during prompt engineering than the ones present
            in the default template, you can provide them here.
        r   template_variables N)_template_string
_variables_required_variablesr   r   _envfrom_stringr   parser   Zfind_undeclared_variableslistr   strr   r   r	   Zset_input_typesZset_input_type)selfr   r   r   astr   Zstatic_input_slotsvar r   O/tmp/pip-unpacked-wheel-z752163x/haystack/components/builders/prompt_builder.py__init__   s"    


zPromptBuilder.__init__)returnc                 C   s   t | | j| j| jdS )z
        Returns a dictionary representation of the component.

        :returns:
            Serialized dictionary representation of the component.
        )r   r   r   )r
   r   r   r   )r   r   r   r   to_dict   s       zPromptBuilder.to_dict)promptr   c                 K   sV   |pi }|pi }||}|  t|  | j}|dk	rD| j|}||}d|iS )a   
        Renders the prompt template with the provided variables.

        It applies the template variables to render the final prompt. You can provide variables via pipeline kwargs.
        In order to overwrite the default template, you can set the `template` parameter.
        In order to overwrite pipeline kwargs, you can set the `template_variables` parameter.

        :param template:
            An optional string template to overwrite PromptBuilder's default template. If None, the default template
            provided at initialization is used.
        :param template_variables:
            An optional dictionary of template variables to overwrite the pipeline variables.
        :param kwargs:
            Pipeline variables used for rendering the prompt.

        :returns: A dictionary with the following keys:
            - `prompt`: The updated prompt text after rendering the prompt template.

        :raises ValueError:
            If any of the required template variables is not provided.
        Nr"   )_validate_variablessetkeysr   r   r   render)r   r   r   kwargsZtemplate_variables_combinedZcompiled_templateresultr   r   r   run   s    
zPromptBuilder.runprovided_variablesc                    sD    fdd| j D }|r@d|}td| d| j  d  ddS )	a  
        Checks if all the required template variables are provided.

        :param provided_variables:
            A set of provided template variables.
        :raises ValueError:
            If any of the required template variables is not provided.
        c                    s   g | ]}| kr|qS r   r   ).0r   r*   r   r   
<listcomp>   s      z5PromptBuilder._validate_variables.<locals>.<listcomp>z, z3Missing required input variables in PromptBuilder: z. Required variables: z. Provided variables: .N)r   join
ValueError)r   r+   Zmissing_variablesZmissing_vars_strr   r*   r   r#      s    	
z!PromptBuilder._validate_variables)NN)NN)__name__
__module____qualname____doc__r   r   r   r   r   r   r!   r	   Zoutput_typesr)   r   r#   r   r   r   r   r      s        
 
(
$"r   N)typingr   r   r   r   r   Zjinja2r   Zjinja2.sandboxr   Zhaystackr	   r
   r   r   r   r   r   <module>   s
   