o
    Jjgu;                     @  s  d Z ddlmZ ddlmZ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 ddlmZ ddlmZ dd	lmZmZ dd
lmZ ddlmZmZ ddlmZ ddlm Z  d"ddZ!d#ddZ"zddl#m$Z$ eddddG dd deZ%W d!S  e&y   G d d dZ%Y d!S w )$zMChain that makes API calls and summarizes the responses to answer a question.    )annotations)AnyDictListOptionalSequenceTuple)urlparse)
deprecated)AsyncCallbackManagerForChainRunCallbackManagerForChainRun)BaseLanguageModel)BasePromptTemplate)Fieldmodel_validator)Self)API_RESPONSE_PROMPTAPI_URL_PROMPT)Chain)LLMChainurlstrreturnTuple[str, str]c                 C  s   t | }|j|jfS )zExtract the scheme + domain from a given URL.

    Args:
        url (str): The input URL.

    Returns:
        return a 2-tuple of scheme and domain
    )r	   schemenetloc)r   
parsed_uri r   Q/var/www/html/zoom/venv/lib/python3.10/site-packages/langchain/chains/api/base.py_extract_scheme_and_domain   s   	r   limit_to_domainsSequence[str]boolc                 C  s<   t | \}}|D ]}t |\}}||kr||kr dS qdS )zCheck if a URL is in the allowed domains.

    Args:
        url (str): The input URL.
        limit_to_domains (Sequence[str]): The allowed domains.

    Returns:
        bool: True if the URL is in the allowed domains, False otherwise.
    TF)r   )r   r    r   domainallowed_domainallowed_schemer   r   r   _check_in_allowed_domain$   s   
r&   )TextRequestsWrapperz0.2.13zThis class is deprecated and will be removed in langchain 1.0. See API reference for replacement: https://api.python.langchain.com/en/latest/chains/langchain.chains.api.base.APIChain.htmlz1.0)sincemessageremovalc                   @  s  e Zd ZU dZded< ded< eddZded< d	ed
< dZd	ed< dZd	ed< ee	dZ
ded< 	 ed<ddZed<ddZeddd=ddZedded>d!d"Zeddd=d#d$Z	%d?d@d+d,Z	%d?dAd.d/Zed%eee fdBd8d9ZedCd:d;Zd%S )DAPIChaina  Chain that makes API calls and summarizes the responses to answer a question.

        *Security Note*: This API chain uses the requests toolkit
            to make GET, POST, PATCH, PUT, and DELETE requests to an API.

            Exercise care in who is allowed to use this chain. If exposing
            to end users, consider that users will be able to make arbitrary
            requests on behalf of the server hosting the code. For example,
            users could ask the server to make a request to a private API
            that is only accessible from the server.

            Control access to who can submit issue requests using this toolkit and
            what network access it has.

            See https://python.langchain.com/docs/security for more information.

        Note: this class is deprecated. See below for a replacement implementation
        using LangGraph. The benefits of this implementation are:

        - Uses LLM tool calling features to encourage properly-formatted API requests;
        - Support for both token-by-token and step-by-step streaming;
        - Support for checkpointing and memory of chat history;
        - Easier to modify or extend (e.g., with additional tools, structured responses, etc.)

        Install LangGraph with:

        .. code-block:: bash

            pip install -U langgraph

        .. code-block:: python

            from typing import Annotated, Sequence
            from typing_extensions import TypedDict

            from langchain.chains.api.prompt import API_URL_PROMPT
            from langchain_community.agent_toolkits.openapi.toolkit import RequestsToolkit
            from langchain_community.utilities.requests import TextRequestsWrapper
            from langchain_core.messages import BaseMessage
            from langchain_core.prompts import ChatPromptTemplate
            from langchain_openai import ChatOpenAI
            from langchain_core.runnables import RunnableConfig
            from langgraph.graph import END, StateGraph
            from langgraph.graph.message import add_messages
            from langgraph.prebuilt.tool_node import ToolNode

            # NOTE: There are inherent risks in giving models discretion
            # to execute real-world actions. We must "opt-in" to these
            # risks by setting allow_dangerous_request=True to use these tools.
            # This can be dangerous for calling unwanted requests. Please make
            # sure your custom OpenAPI spec (yaml) is safe and that permissions
            # associated with the tools are narrowly-scoped.
            ALLOW_DANGEROUS_REQUESTS = True

            # Subset of spec for https://jsonplaceholder.typicode.com
            api_spec = """
            openapi: 3.0.0
            info:
              title: JSONPlaceholder API
              version: 1.0.0
            servers:
              - url: https://jsonplaceholder.typicode.com
            paths:
              /posts:
                get:
                  summary: Get posts
                  parameters: &id001
                    - name: _limit
                      in: query
                      required: false
                      schema:
                        type: integer
                      example: 2
                      description: Limit the number of results
            """

            llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
            toolkit = RequestsToolkit(
                requests_wrapper=TextRequestsWrapper(headers={}),  # no auth required
                allow_dangerous_requests=ALLOW_DANGEROUS_REQUESTS,
            )
            tools = toolkit.get_tools()

            api_request_chain = (
                API_URL_PROMPT.partial(api_docs=api_spec)
                | llm.bind_tools(tools, tool_choice="any")
            )

            class ChainState(TypedDict):
                """LangGraph state."""

                messages: Annotated[Sequence[BaseMessage], add_messages]


            async def acall_request_chain(state: ChainState, config: RunnableConfig):
                last_message = state["messages"][-1]
                response = await api_request_chain.ainvoke(
                    {"question": last_message.content}, config
                )
                return {"messages": [response]}

            async def acall_model(state: ChainState, config: RunnableConfig):
                response = await llm.ainvoke(state["messages"], config)
                return {"messages": [response]}

            graph_builder = StateGraph(ChainState)
            graph_builder.add_node("call_tool", acall_request_chain)
            graph_builder.add_node("execute_tool", ToolNode(tools))
            graph_builder.add_node("call_model", acall_model)
            graph_builder.set_entry_point("call_tool")
            graph_builder.add_edge("call_tool", "execute_tool")
            graph_builder.add_edge("execute_tool", "call_model")
            graph_builder.add_edge("call_model", END)
            chain = graph_builder.compile()

        .. code-block:: python

            example_query = "Fetch the top two posts. What are their titles?"

            events = chain.astream(
                {"messages": [("user", example_query)]},
                stream_mode="values",
            )
            async for event in events:
                event["messages"][-1].pretty_print()
        r   api_request_chainapi_answer_chainT)excluder'   requests_wrapperr   api_docsquestionquestion_keyoutput
output_key)default_factoryOptional[Sequence[str]]r    r   	List[str]c                 C     | j gS )z:Expect input key.

            :meta private:
            )r2   selfr   r   r   
input_keys      zAPIChain.input_keysc                 C  r8   )z;Expect output key.

            :meta private:
            )r4   r9   r   r   r   output_keys   r<   zAPIChain.output_keysafter)moder   c                 C  s6   | j jj}ddh}t||krtd| d| | S )z:Check that api request prompt expects the right variables.r1   r0   Input variables should be , got )r,   promptinput_variablesset
ValueErrorr:   
input_varsexpected_varsr   r   r   validate_api_request_prompt      
z$APIChain.validate_api_request_promptbeforevaluesr   r   c                 C  s0   d|vrt d|d s|d durt d|S )z%Check that allowed domains are valid.r    zKYou must specify a list of domains to limit access using `limit_to_domains`NzJPlease provide a list of domains to limit access using `limit_to_domains`.)rE   )clsrL   r   r   r   validate_limit_to_domains   s   z"APIChain.validate_limit_to_domainsc                 C  s6   | j jj}h d}t||krtd| d| | S )z9Check that api answer prompt expects the right variables.>   api_urlr0   r1   api_responser@   rA   )r-   rB   rC   rD   rE   rF   r   r   r   validate_api_answer_prompt  rJ   z#APIChain.validate_api_answer_promptNinputsDict[str, Any]run_manager$Optional[CallbackManagerForChainRun]Dict[str, str]c                 C  s   |pt  }|| j }| jj|| j| d}|j|dd| jd |	 }| j
r8t|| j
s8t| d| j
 | j|}|jt|dd| jd | jj|| j||| d}| j|iS N)r1   r0   	callbacksgreen
)colorendverbosez  is not in the allowed domains: yellow)r1   r0   rO   rP   rX   )r   get_noop_managerr2   r,   predictr0   	get_childon_textr]   stripr    r&   rE   r/   getr   r-   r4   r:   rR   rT   _run_managerr1   rO   rP   answerr   r   r   _call  s6   

zAPIChain._call)Optional[AsyncCallbackManagerForChainRun]c                   s   |pt  }|| j }| jj|| j| dI d H }|j|dd| jdI d H  |	 }| j
r?t|| j
s?t| d| j
 | j|I d H }|jt|dd| jdI d H  | jj|| j||| dI d H }| j|iS rW   )r   r_   r2   r,   apredictr0   ra   rb   r]   rc   r    r&   rE   r/   agetr   r-   r4   re   r   r   r   _acall3  s>   



zAPIChain._acallllmr   headersOptional[dict]api_url_promptr   api_response_promptkwargsc                 K  s<   t ||d}t|d}	t ||d}
| d||
|	||d|S )z-Load chain from just an LLM and the api docs.)rm   rB   )rn   )r,   r-   r/   r0   r    Nr   )r   r'   )rM   rm   r0   rn   rp   rq   r    rr   get_request_chainr/   get_answer_chainr   r   r   from_llm_and_api_docsX  s   
zAPIChain.from_llm_and_api_docsc                 C  s   dS )N	api_chainr   r9   r   r   r   _chain_typep  s   zAPIChain._chain_type)r   r7   )r   r   )rL   r   r   r   )N)rR   rS   rT   rU   r   rV   )rR   rS   rT   ri   r   rV   )rm   r   r0   r   rn   ro   rp   r   rq   r   r    r6   rr   r   r   r+   )r   r   )__name__
__module____qualname____doc____annotations__r   r/   r2   r4   listr    propertyr;   r=   r   rI   classmethodrN   rQ   rh   rl   r   r   tupleru   rw   r   r   r   r   r+   :   sB   
 

$%r+   c                   @  s   e Zd Zd	ddZdS )
r+   argsr   rr   r   Nonec                 O  s   t d)NzeTo use the APIChain, you must install the langchain_community package.pip install langchain_community)ImportError)r:   r   rr   r   r   r   __init__v  s   zAPIChain.__init__N)r   r   rr   r   r   r   )rx   ry   rz   r   r   r   r   r   r+   u  s    N)r   r   r   r   )r   r   r    r!   r   r"   )'r{   
__future__r   typingr   r   r   r   r   r   urllib.parser	   langchain_core._apir
   langchain_core.callbacksr   r   langchain_core.language_modelsr   langchain_core.promptsr   pydanticr   r   typing_extensionsr   langchain.chains.api.promptr   r   langchain.chains.baser   langchain.chains.llmr   r   r&   &langchain_community.utilities.requestsr'   r+   r   r   r   r   r   <module>   s8     

	  2