Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
chain.py231 linesDownload Raw Back to openapi
1"""Chain that makes API calls and summarizes the responses to answer a question."""2 3from __future__ import annotations4 5import json6from typing import Any, Dict, List, NamedTuple, Optional, cast7 8from langchain_classic.chains.api.openapi.requests_chain import APIRequesterChain9from langchain_classic.chains.api.openapi.response_chain import APIResponderChain10from langchain_classic.chains.base import Chain11from langchain_classic.chains.llm import LLMChain12from langchain_core.callbacks import CallbackManagerForChainRun, Callbacks13from langchain_core.language_models import BaseLanguageModel14from pydantic import BaseModel, Field15from requests import Response16 17from langchain_community.tools.openapi.utils.api_models import APIOperation18from langchain_community.utilities.requests import Requests19 20 21class _ParamMapping(NamedTuple):22    """Mapping from parameter name to parameter value."""23 24    query_params: List[str]25    body_params: List[str]26    path_params: List[str]27 28 29class OpenAPIEndpointChain(Chain, BaseModel):30    """Chain interacts with an OpenAPI endpoint using natural language."""31 32    api_request_chain: LLMChain33    api_response_chain: Optional[LLMChain] = None34    api_operation: APIOperation35    requests: Requests = Field(exclude=True, default_factory=Requests)36    param_mapping: _ParamMapping = Field(alias="param_mapping")37    return_intermediate_steps: bool = False38    instructions_key: str = "instructions"  #: :meta private:39    output_key: str = "output"  #: :meta private:40    max_text_length: Optional[int] = Field(ge=0)  #: :meta private:41 42    @property43    def input_keys(self) -> List[str]:44        """Expect input key.45 46        :meta private:47        """48        return [self.instructions_key]49 50    @property51    def output_keys(self) -> List[str]:52        """Expect output key.53 54        :meta private:55        """56        if not self.return_intermediate_steps:57            return [self.output_key]58        else:59            return [self.output_key, "intermediate_steps"]60 61    def _construct_path(self, args: Dict[str, str]) -> str:62        """Construct the path from the deserialized input."""63        path = self.api_operation.base_url + self.api_operation.path64        for param in self.param_mapping.path_params:65            path = path.replace(f"{{{param}}}", str(args.pop(param, "")))66        return path67 68    def _extract_query_params(self, args: Dict[str, str]) -> Dict[str, str]:69        """Extract the query params from the deserialized input."""70        query_params = {}71        for param in self.param_mapping.query_params:72            if param in args:73                query_params[param] = args.pop(param)74        return query_params75 76    def _extract_body_params(self, args: Dict[str, str]) -> Optional[Dict[str, str]]:77        """Extract the request body params from the deserialized input."""78        body_params = None79        if self.param_mapping.body_params:80            body_params = {}81            for param in self.param_mapping.body_params:82                if param in args:83                    body_params[param] = args.pop(param)84        return body_params85 86    def deserialize_json_input(self, serialized_args: str) -> dict:87        """Use the serialized typescript dictionary.88 89        Resolve the path, query params dict, and optional requestBody dict.90        """91        args: dict = json.loads(serialized_args)92        path = self._construct_path(args)93        body_params = self._extract_body_params(args)94        query_params = self._extract_query_params(args)95        return {96            "url": path,97            "data": body_params,98            "params": query_params,99        }100 101    def _get_output(self, output: str, intermediate_steps: dict) -> dict:102        """Return the output from the API call."""103        if self.return_intermediate_steps:104            return {105                self.output_key: output,106                "intermediate_steps": intermediate_steps,107            }108        else:109            return {self.output_key: output}110 111    def _call(112        self,113        inputs: Dict[str, Any],114        run_manager: Optional[CallbackManagerForChainRun] = None,115    ) -> Dict[str, str]:116        _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager()117        intermediate_steps = {}118        instructions = inputs[self.instructions_key]119        instructions = instructions[: self.max_text_length]120        _api_arguments = self.api_request_chain.predict_and_parse(121            instructions=instructions, callbacks=_run_manager.get_child()122        )123        api_arguments = cast(str, _api_arguments)124        intermediate_steps["request_args"] = api_arguments125        _run_manager.on_text(126            api_arguments, color="green", end="\n", verbose=self.verbose127        )128        if api_arguments.startswith("ERROR"):129            return self._get_output(api_arguments, intermediate_steps)130        elif api_arguments.startswith("MESSAGE:"):131            return self._get_output(132                api_arguments[len("MESSAGE:") :], intermediate_steps133            )134        try:135            request_args = self.deserialize_json_input(api_arguments)136            method = getattr(self.requests, self.api_operation.method.value)137            api_response: Response = method(**request_args)138            if api_response.status_code != 200:139                method_str = str(self.api_operation.method.value)140                response_text = (141                    f"{api_response.status_code}: {api_response.reason}"142                    + f"\nFor {method_str.upper()}  {request_args['url']}\n"143                    + f"Called with args: {request_args['params']}"144                )145            else:146                response_text = api_response.text147        except Exception as e:148            response_text = f"Error with message {str(e)}"149        response_text = response_text[: self.max_text_length]150        intermediate_steps["response_text"] = response_text151        _run_manager.on_text(152            response_text, color="blue", end="\n", verbose=self.verbose153        )154        if self.api_response_chain is not None:155            _answer = self.api_response_chain.predict_and_parse(156                response=response_text,157                instructions=instructions,158                callbacks=_run_manager.get_child(),159            )160            answer = cast(str, _answer)161            _run_manager.on_text(answer, color="yellow", end="\n", verbose=self.verbose)162            return self._get_output(answer, intermediate_steps)163        else:164            return self._get_output(response_text, intermediate_steps)165 166    @classmethod167    def from_url_and_method(168        cls,169        spec_url: str,170        path: str,171        method: str,172        llm: BaseLanguageModel,173        requests: Optional[Requests] = None,174        return_intermediate_steps: bool = False,175        **kwargs: Any,176        # TODO: Handle async177    ) -> "OpenAPIEndpointChain":178        """Create an OpenAPIEndpoint from a spec at the specified url."""179        operation = APIOperation.from_openapi_url(spec_url, path, method)180        return cls.from_api_operation(181            operation,182            requests=requests,183            llm=llm,184            return_intermediate_steps=return_intermediate_steps,185            **kwargs,186        )187 188    @classmethod189    def from_api_operation(190        cls,191        operation: APIOperation,192        llm: BaseLanguageModel,193        requests: Optional[Requests] = None,194        verbose: bool = False,195        return_intermediate_steps: bool = False,196        raw_response: bool = False,197        callbacks: Callbacks = None,198        **kwargs: Any,199        # TODO: Handle async200    ) -> "OpenAPIEndpointChain":201        """Create an OpenAPIEndpointChain from an operation and a spec."""202        param_mapping = _ParamMapping(203            query_params=operation.query_params,204            body_params=operation.body_params,205            path_params=operation.path_params,206        )207        requests_chain = APIRequesterChain.from_llm_and_typescript(208            llm,209            typescript_definition=operation.to_typescript(),210            verbose=verbose,211            callbacks=callbacks,212        )213        if raw_response:214            response_chain = None215        else:216            response_chain = APIResponderChain.from_llm(217                llm, verbose=verbose, callbacks=callbacks218            )219        _requests = requests or Requests()220        return cls(221            api_request_chain=requests_chain,222            api_response_chain=response_chain,223            api_operation=operation,224            requests=_requests,225            param_mapping=param_mapping,226            verbose=verbose,227            return_intermediate_steps=return_intermediate_steps,228            callbacks=callbacks,229            **kwargs,230        )231 
codekingpro/portable-devtools · Team Ai