From fcb3a647997c6275e3d341abb032e5106ea39cac Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Tue, 27 Jun 2023 18:09:03 +0200 Subject: [PATCH] Add support for passing headers and search params to openai openapi chain (#6782) - Description: add support for passing headers and search params to OpenAI OpenAPI chains. - Issue: n/a - Dependencies: n/a - Tag maintainer: @hwchase17 - Twitter handle: @pelaseyed --------- Co-authored-by: Dev 2049 --- langchain/chains/openai_functions/openapi.py | 25 +- .../chains/openai_functions/__init__.py | 0 .../chains/openai_functions/test_openapi.py | 25 ++ .../brandfetch-brandfetch-2.0.0-resolved.json | 282 ++++++++++++++++++ 4 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 tests/integration_tests/chains/openai_functions/__init__.py create mode 100644 tests/integration_tests/chains/openai_functions/test_openapi.py create mode 100644 tests/integration_tests/examples/brandfetch-brandfetch-2.0.0-resolved.json diff --git a/langchain/chains/openai_functions/openapi.py b/langchain/chains/openai_functions/openapi.py index 5a29bb687d..9e8558e7e8 100644 --- a/langchain/chains/openai_functions/openapi.py +++ b/langchain/chains/openai_functions/openapi.py @@ -157,7 +157,13 @@ def openapi_spec_to_openai_fn( "url": api_op.base_url + api_op.path, } - def default_call_api(name: str, fn_args: dict, **kwargs: Any) -> Any: + def default_call_api( + name: str, + fn_args: dict, + headers: Optional[dict] = None, + params: Optional[dict] = None, + **kwargs: Any, + ) -> Any: method = _name_to_call_map[name]["method"] url = _name_to_call_map[name]["url"] path_params = fn_args.pop("path_params", {}) @@ -165,6 +171,16 @@ def openapi_spec_to_openai_fn( if "data" in fn_args and isinstance(fn_args["data"], dict): fn_args["data"] = json.dumps(fn_args["data"]) _kwargs = {**fn_args, **kwargs} + if headers is not None: + if "headers" in _kwargs: + _kwargs["headers"].update(headers) + else: + _kwargs["headers"] = headers + if params is not None: + if "params" in _kwargs: + _kwargs["params"].update(params) + else: + _kwargs["params"] = params return requests.request(method, url, **_kwargs) return functions, default_call_api @@ -218,6 +234,8 @@ def get_openapi_chain( request_chain: Optional[Chain] = None, llm_kwargs: Optional[Dict] = None, verbose: bool = False, + headers: Optional[Dict] = None, + params: Optional[Dict] = None, **kwargs: Any, ) -> SequentialChain: """Create a chain for querying an API from a OpenAPI spec. @@ -259,7 +277,10 @@ def get_openapi_chain( **(llm_kwargs or {}), ) request_chain = request_chain or SimpleRequestChain( - request_method=call_api_fn, verbose=verbose + request_method=lambda name, args: call_api_fn( + name, args, headers=headers, params=params + ), + verbose=verbose, ) return SequentialChain( chains=[llm_chain, request_chain], diff --git a/tests/integration_tests/chains/openai_functions/__init__.py b/tests/integration_tests/chains/openai_functions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration_tests/chains/openai_functions/test_openapi.py b/tests/integration_tests/chains/openai_functions/test_openapi.py new file mode 100644 index 0000000000..5d7ea309f3 --- /dev/null +++ b/tests/integration_tests/chains/openai_functions/test_openapi.py @@ -0,0 +1,25 @@ +import os +from pathlib import Path + +from langchain.chains.openai_functions.openapi import get_openapi_chain + + +def test_openai_opeanapi() -> None: + chain = get_openapi_chain( + "https://www.klarna.com/us/shopping/public/openai/v0/api-docs/" + ) + output = chain.run("What are some options for a men's large blue button down shirt") + + assert isinstance(output, dict) + + +def test_openai_opeanapi_headers() -> None: + BRANDFETCH_API_KEY = os.environ.get("BRANDFETCH_API_KEY") + headers = {"Authorization": f"Bearer {BRANDFETCH_API_KEY}"} + file_path = str( + Path(__file__).parents[2] / "examples/brandfetch-brandfetch-2.0.0-resolved.json" + ) + chain = get_openapi_chain(file_path, headers=headers) + output = chain.run("I want to know about nike.comgg") + + assert isinstance(output, str) diff --git a/tests/integration_tests/examples/brandfetch-brandfetch-2.0.0-resolved.json b/tests/integration_tests/examples/brandfetch-brandfetch-2.0.0-resolved.json new file mode 100644 index 0000000000..de37dbf5fb --- /dev/null +++ b/tests/integration_tests/examples/brandfetch-brandfetch-2.0.0-resolved.json @@ -0,0 +1,282 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Brandfetch API", + "description": "Brandfetch API (v2) for retrieving brand information.\n\nSee our [documentation](https://docs.brandfetch.com/) for further details. ", + "termsOfService": "https://brandfetch.com/terms", + "contact": { + "url": "https://brandfetch.com/developers" + }, + "version": "2.0.0" + }, + "externalDocs": { + "description": "Documentation", + "url": "https://docs.brandfetch.com/" + }, + "servers": [ + { + "url": "https://api.brandfetch.io/v2" + } + ], + "paths": { + "/brands/{domainOrId}": { + "get": { + "summary": "Retrieve a brand", + "description": "Fetch brand information by domain or ID\n\nFurther details here: https://docs.brandfetch.com/reference/retrieve-brand\n", + "parameters": [ + { + "name": "domainOrId", + "in": "path", + "description": "Domain or ID of the brand", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Brand data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Brand" + }, + "examples": { + "brandfetch.com": { + "value": "{\"name\":\"Brandfetch\",\"domain\":\"brandfetch.com\",\"claimed\":true,\"description\":\"All brands. In one place\",\"links\":[{\"name\":\"twitter\",\"url\":\"https://twitter.com/brandfetch\"},{\"name\":\"linkedin\",\"url\":\"https://linkedin.com/company/brandfetch\"}],\"logos\":[{\"type\":\"logo\",\"theme\":\"light\",\"formats\":[{\"src\":\"https://asset.brandfetch.io/idL0iThUh6/id9WE9j86h.svg\",\"background\":\"transparent\",\"format\":\"svg\",\"size\":15555}]},{\"type\":\"logo\",\"theme\":\"dark\",\"formats\":[{\"src\":\"https://asset.brandfetch.io/idL0iThUh6/idWbsK1VCy.png\",\"background\":\"transparent\",\"format\":\"png\",\"height\":215,\"width\":800,\"size\":33937},{\"src\":\"https://asset.brandfetch.io/idL0iThUh6/idtCMfbWO0.svg\",\"background\":\"transparent\",\"format\":\"svg\",\"height\":null,\"width\":null,\"size\":15567}]},{\"type\":\"symbol\",\"theme\":\"light\",\"formats\":[{\"src\":\"https://asset.brandfetch.io/idL0iThUh6/idXGq6SIu2.svg\",\"background\":\"transparent\",\"format\":\"svg\",\"size\":2215}]},{\"type\":\"symbol\",\"theme\":\"dark\",\"formats\":[{\"src\":\"https://asset.brandfetch.io/idL0iThUh6/iddCQ52AR5.svg\",\"background\":\"transparent\",\"format\":\"svg\",\"size\":2215}]},{\"type\":\"icon\",\"theme\":\"dark\",\"formats\":[{\"src\":\"https://asset.brandfetch.io/idL0iThUh6/idls3LaPPQ.png\",\"background\":null,\"format\":\"png\",\"height\":400,\"width\":400,\"size\":2565}]}],\"colors\":[{\"hex\":\"#0084ff\",\"type\":\"accent\",\"brightness\":113},{\"hex\":\"#00193E\",\"type\":\"brand\",\"brightness\":22},{\"hex\":\"#F03063\",\"type\":\"brand\",\"brightness\":93},{\"hex\":\"#7B0095\",\"type\":\"brand\",\"brightness\":37},{\"hex\":\"#76CC4B\",\"type\":\"brand\",\"brightness\":176},{\"hex\":\"#FFDA00\",\"type\":\"brand\",\"brightness\":210},{\"hex\":\"#000000\",\"type\":\"dark\",\"brightness\":0},{\"hex\":\"#ffffff\",\"type\":\"light\",\"brightness\":255}],\"fonts\":[{\"name\":\"Poppins\",\"type\":\"title\",\"origin\":\"google\",\"originId\":\"Poppins\",\"weights\":[]},{\"name\":\"Inter\",\"type\":\"body\",\"origin\":\"google\",\"originId\":\"Inter\",\"weights\":[]}],\"images\":[{\"type\":\"banner\",\"formats\":[{\"src\":\"https://asset.brandfetch.io/idL0iThUh6/idUuia5imo.png\",\"background\":\"transparent\",\"format\":\"png\",\"height\":500,\"width\":1500,\"size\":5539}]}]}" + } + } + } + } + }, + "400": { + "description": "Invalid domain or ID supplied" + }, + "404": { + "description": "The brand does not exist or the domain can't be resolved." + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + }, + "components": { + "schemas": { + "Brand": { + "required": [ + "claimed", + "colors", + "description", + "domain", + "fonts", + "images", + "links", + "logos", + "name" + ], + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageAsset" + } + }, + "fonts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FontAsset" + } + }, + "domain": { + "type": "string" + }, + "claimed": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Brand_links" + } + }, + "logos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageAsset" + } + }, + "colors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ColorAsset" + } + } + }, + "description": "Object representing a brand" + }, + "ColorAsset": { + "required": [ + "brightness", + "hex", + "type" + ], + "type": "object", + "properties": { + "brightness": { + "type": "integer" + }, + "hex": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "accent", + "brand", + "customizable", + "dark", + "light", + "vibrant" + ] + } + }, + "description": "Brand color asset" + }, + "FontAsset": { + "type": "object", + "properties": { + "originId": { + "type": "string" + }, + "origin": { + "type": "string", + "enum": [ + "adobe", + "custom", + "google", + "system" + ] + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "weights": { + "type": "array", + "items": { + "type": "number" + } + }, + "items": { + "type": "string" + } + }, + "description": "Brand font asset" + }, + "ImageAsset": { + "required": [ + "formats", + "theme", + "type" + ], + "type": "object", + "properties": { + "formats": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageFormat" + } + }, + "theme": { + "type": "string", + "enum": [ + "light", + "dark" + ] + }, + "type": { + "type": "string", + "enum": [ + "logo", + "icon", + "symbol", + "banner" + ] + } + }, + "description": "Brand image asset" + }, + "ImageFormat": { + "required": [ + "background", + "format", + "size", + "src" + ], + "type": "object", + "properties": { + "size": { + "type": "integer" + }, + "src": { + "type": "string" + }, + "background": { + "type": "string", + "enum": [ + "transparent" + ] + }, + "format": { + "type": "string" + }, + "width": { + "type": "integer" + }, + "height": { + "type": "integer" + } + }, + "description": "Brand image asset image format" + }, + "Brand_links": { + "required": [ + "name", + "url" + ], + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + } + }, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "API Key" + } + } + } +} \ No newline at end of file