Overuse may indicate design issues; consider polymorphism
if isinstance(val, list):
1from __future__ import annotations23import json4import logging5import re6from collections import defaultdict7from collections.abc import Callable8from typing import TYPE_CHECKING, Any910import requests11from langchain_core._api import deprecated12from langchain_core.callbacks import CallbackManagerForChainRun13from langchain_core.language_models import BaseLanguageModel14from langchain_core.output_parsers.openai_functions import JsonOutputFunctionsParser15from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate16from langchain_core.utils.input import get_colored_text17from requests import JSONDecodeError, Response18from typing_extensions import override1920from langchain_classic.chains.base import Chain21from langchain_classic.chains.llm import LLMChain22from langchain_classic.chains.sequential import SequentialChain2324if TYPE_CHECKING:25 from langchain_community.utilities.openapi import OpenAPISpec26 from openapi_pydantic import Parameter2728_logger = logging.getLogger(__name__)2930_OPENAPI_REPLACEMENT = (31 "Bind your OpenAPI operations as tools on a chat model with "32 "`ChatModel.bind_tools(...)` and execute the resulting tool calls with an "33 "HTTP client (e.g. `requests` or `httpx`)."34)353637def _format_url(url: str, path_params: dict) -> str:38 expected_path_param = re.findall(r"{(.*?)}", url)39 new_params = {}40 for param in expected_path_param:41 clean_param = param.lstrip(".;").rstrip("*")42 val = path_params[clean_param]43 if isinstance(val, list):44 if param[0] == ".":45 sep = "." if param[-1] == "*" else ","46 new_val = "." + sep.join(val)47 elif param[0] == ";":48 sep = f"{clean_param}=" if param[-1] == "*" else ","49 new_val = f"{clean_param}=" + sep.join(val)50 else:51 new_val = ",".join(val)52 elif isinstance(val, dict):53 kv_sep = "=" if param[-1] == "*" else ","54 kv_strs = [kv_sep.join((k, v)) for k, v in val.items()]55 if param[0] == ".":56 sep = "."57 new_val = "."58 elif param[0] == ";":59 sep = ";"60 new_val = ";"61 else:62 sep = ","63 new_val = ""64 new_val += sep.join(kv_strs)65 elif param[0] == ".":66 new_val = f".{val}"67 elif param[0] == ";":68 new_val = f";{clean_param}={val}"69 else:70 new_val = val71 new_params[param] = new_val72 return url.format(**new_params)737475def _openapi_params_to_json_schema(params: list[Parameter], spec: OpenAPISpec) -> dict:76 properties = {}77 required = []78 for p in params:79 if p.param_schema:80 schema = spec.get_schema(p.param_schema)81 else:82 media_type_schema = next(iter(p.content.values())).media_type_schema83 schema = spec.get_schema(media_type_schema)84 if p.description and not schema.description:85 schema.description = p.description86 properties[p.name] = json.loads(schema.json(exclude_none=True))87 if p.required:88 required.append(p.name)89 return {"type": "object", "properties": properties, "required": required}909192@deprecated(93 since="1.0.4",94 removal="2.0.0",95 addendum=_OPENAPI_REPLACEMENT,96)97def openapi_spec_to_openai_fn(98 spec: OpenAPISpec,99) -> tuple[list[dict[str, Any]], Callable]:100 """OpenAPI spec to OpenAI function JSON Schema.101102 Convert a valid OpenAPI spec to the JSON Schema format expected for OpenAI103 functions.104105 Args:106 spec: OpenAPI spec to convert.107108 Returns:109 Tuple of the OpenAI functions JSON schema and a default function for executing110 a request based on the OpenAI function schema.111 """112 try:113 from langchain_community.tools import APIOperation114 except ImportError as e:115 msg = (116 "Could not import langchain_community.tools. "117 "Please install it with `pip install langchain-community`."118 )119 raise ImportError(msg) from e120121 if not spec.paths:122 return [], lambda: None123 functions = []124 _name_to_call_map = {}125 for path in spec.paths:126 path_params = {127 (p.name, p.param_in): p for p in spec.get_parameters_for_path(path)128 }129 for method in spec.get_methods_for_path(path):130 request_args = {}131 op = spec.get_operation(path, method)132 op_params = path_params.copy()133 for param in spec.get_parameters_for_operation(op):134 op_params[(param.name, param.param_in)] = param135 params_by_type = defaultdict(list)136 for name_loc, p in op_params.items():137 params_by_type[name_loc[1]].append(p)138 param_loc_to_arg_name = {139 "query": "params",140 "header": "headers",141 "cookie": "cookies",142 "path": "path_params",143 }144 for param_loc, arg_name in param_loc_to_arg_name.items():145 if params_by_type[param_loc]:146 request_args[arg_name] = _openapi_params_to_json_schema(147 params_by_type[param_loc],148 spec,149 )150 request_body = spec.get_request_body_for_operation(op)151 # TODO: Support more MIME types.152 if request_body and request_body.content:153 media_types = {}154 for media_type, media_type_object in request_body.content.items():155 if media_type_object.media_type_schema:156 schema = spec.get_schema(media_type_object.media_type_schema)157 media_types[media_type] = json.loads(158 schema.json(exclude_none=True),159 )160 if len(media_types) == 1:161 media_type, schema_dict = next(iter(media_types.items()))162 key = "json" if media_type == "application/json" else "data"163 request_args[key] = schema_dict164 elif len(media_types) > 1:165 request_args["data"] = {"anyOf": list(media_types.values())}166167 api_op = APIOperation.from_openapi_spec(spec, path, method)168 fn = {169 "name": api_op.operation_id,170 "description": api_op.description,171 "parameters": {172 "type": "object",173 "properties": request_args,174 },175 }176 functions.append(fn)177 _name_to_call_map[fn["name"]] = {178 "method": method,179 "url": api_op.base_url + api_op.path,180 }181182 def default_call_api(183 name: str,184 fn_args: dict,185 headers: dict | None = None,186 params: dict | None = None,187 timeout: int | None = 30,188 **kwargs: Any,189 ) -> Any:190 method = _name_to_call_map[name]["method"]191 url = _name_to_call_map[name]["url"]192 path_params = fn_args.pop("path_params", {})193 url = _format_url(url, path_params)194 if "data" in fn_args and isinstance(fn_args["data"], dict):195 fn_args["data"] = json.dumps(fn_args["data"])196 _kwargs = {**fn_args, **kwargs}197 if headers is not None:198 if "headers" in _kwargs:199 _kwargs["headers"].update(headers)200 else:201 _kwargs["headers"] = headers202 if params is not None:203 if "params" in _kwargs:204 _kwargs["params"].update(params)205 else:206 _kwargs["params"] = params207 return requests.request(method, url, **_kwargs, timeout=timeout)208209 return functions, default_call_api210211212@deprecated(213 since="1.0.4",214 removal="2.0.0",215 addendum=_OPENAPI_REPLACEMENT,216)217class SimpleRequestChain(Chain):218 """Chain for making a simple request to an API endpoint."""219220 request_method: Callable221 """Method to use for making the request."""222223 output_key: str = "response"224 """Key to use for the output of the request."""225226 input_key: str = "function"227 """Key to use for the input of the request."""228229 @property230 @override231 def input_keys(self) -> list[str]:232 return [self.input_key]233234 @property235 @override236 def output_keys(self) -> list[str]:237 return [self.output_key]238239 def _call(240 self,241 inputs: dict[str, Any],242 run_manager: CallbackManagerForChainRun | None = None,243 ) -> dict[str, Any]:244 """Run the logic of this chain and return the output."""245 _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager()246 name = inputs[self.input_key].pop("name")247 args = inputs[self.input_key].pop("arguments")248 _pretty_name = get_colored_text(name, "green")249 _pretty_args = get_colored_text(json.dumps(args, indent=2), "green")250 _text = f"Calling endpoint {_pretty_name} with arguments:\n" + _pretty_args251 _run_manager.on_text(_text)252 api_response: Response = self.request_method(name, args)253 if api_response.status_code != requests.codes.ok:254 response = (255 f"{api_response.status_code}: {api_response.reason}"256 f"\nFor {name} "257 f"Called with args: {args.get('params', '')}"258 )259 else:260 try:261 response = api_response.json()262 except JSONDecodeError:263 response = api_response.text264 except Exception:265 _logger.exception("Unexpected error parsing response as JSON")266 response = api_response.text267 return {self.output_key: response}268269270@deprecated(271 since="0.2.13",272 removal="2.0.0",273 addendum=_OPENAPI_REPLACEMENT,274)275def get_openapi_chain(276 spec: OpenAPISpec | str,277 llm: BaseLanguageModel | None = None,278 prompt: BasePromptTemplate | None = None,279 request_chain: Chain | None = None,280 llm_chain_kwargs: dict | None = None,281 verbose: bool = False, # noqa: FBT001,FBT002282 headers: dict | None = None,283 params: dict | None = None,284 **kwargs: Any,285) -> SequentialChain:286 r"""Create a chain for querying an API from a OpenAPI spec.287288 !!! warning "Deprecated"289 This function and all related utilities in this module are deprecated.290 Use LLM tool calling features directly with an HTTP client instead.291292 Args:293 spec: OpenAPISpec or url/file/text string corresponding to one.294 llm: language model, should be an OpenAI function-calling model.295 prompt: Main prompt template to use.296 request_chain: Chain for taking the functions output and executing the request.297 params: Request parameters.298 headers: Request headers.299 verbose: Whether to run the chain in verbose mode.300 llm_chain_kwargs: LLM chain additional keyword arguments.301 **kwargs: Additional keyword arguments to pass to the chain.302303 """304 try:305 from langchain_community.utilities.openapi import OpenAPISpec306 except ImportError as e:307 msg = (308 "Could not import langchain_community.utilities.openapi. "309 "Please install it with `pip install langchain-community`."310 )311 raise ImportError(msg) from e312 if isinstance(spec, str):313 for conversion in (314 OpenAPISpec.from_url,315 OpenAPISpec.from_file,316 OpenAPISpec.from_text,317 ):318 try:319 spec = conversion(spec)320 break321 except ImportError:322 raise323 except Exception: # noqa: BLE001324 _logger.debug(325 "Parse spec failed for OpenAPISpec.%s",326 conversion.__name__,327 exc_info=True,328 )329 if isinstance(spec, str):330 msg = f"Unable to parse spec from source {spec}"331 raise ValueError(msg) # noqa: TRY004332 openai_fns, call_api_fn = openapi_spec_to_openai_fn(spec)333 if not llm:334 msg = (335 "Must provide an LLM for this chain.For example,\n"336 "from langchain_openai import ChatOpenAI\n"337 "model = ChatOpenAI()\n"338 )339 raise ValueError(msg)340 prompt = prompt or ChatPromptTemplate.from_template(341 "Use the provided API's to respond to this user query:\n\n{query}",342 )343 llm_chain = LLMChain(344 llm=llm,345 prompt=prompt,346 llm_kwargs={"functions": openai_fns},347 output_parser=JsonOutputFunctionsParser(args_only=False),348 output_key="function",349 verbose=verbose,350 **(llm_chain_kwargs or {}),351 )352 request_chain = request_chain or SimpleRequestChain(353 request_method=lambda name, args: call_api_fn(354 name,355 args,356 headers=headers,357 params=params,358 ),359 verbose=verbose,360 )361 return SequentialChain(362 chains=[llm_chain, request_chain],363 input_variables=llm_chain.input_keys,364 output_variables=["response"],365 verbose=verbose,366 **kwargs,367 )
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.