diff --git a/tests/conftest.py b/tests/conftest.py index 3c9837957..e6eb3562f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,9 @@ def pytest_configure(): pytest.COMPLEX_EXAMPLE_PATH = ( Path(__file__).parent.absolute() / "data" / "complex_example.json" ) + pytest.OPENAPI_EXAMPLE_PATH = ( + Path(__file__).parent.absolute() / "data" / "Openapi.json" + ) pytest.CODE_WITH_SYNTAX_ERROR = """ def get_text(): diff --git a/tests/data/Openapi.json b/tests/data/Openapi.json new file mode 100644 index 000000000..d51404bbb --- /dev/null +++ b/tests/data/Openapi.json @@ -0,0 +1,445 @@ +{ + "description": "", + "name": "openapi", + "id": "1", + "data": { + "nodes": [ + { + "width": 384, + "height": 311, + "id": "dndnode_19", + "type": "genericNode", + "position": { + "x": -207.85635949789724, + "y": -105.73915116823618 + }, + "data": { + "type": "JsonToolkit", + "node": { + "template": { + "spec": { + "required": true, + "placeholder": "", + "show": true, + "multiline": false, + "password": false, + "name": "spec", + "type": "JsonSpec", + "list": false + }, + "_type": "JsonToolkit" + }, + "description": "Toolkit for interacting with a JSON spec.", + "base_classes": [ + "BaseToolkit" + ] + }, + "id": "dndnode_19", + "value": null + }, + "selected": false, + "positionAbsolute": { + "x": -207.85635949789724, + "y": -105.73915116823618 + }, + "dragging": false + }, + { + "width": 384, + "height": 351, + "id": "dndnode_32", + "type": "genericNode", + "position": { + "x": 745.308873444751, + "y": -37.007911201107675 + }, + "data": { + "type": "OpenAPIToolkit", + "node": { + "template": { + "json_agent": { + "required": true, + "placeholder": "", + "show": true, + "multiline": false, + "password": false, + "name": "json_agent", + "type": "AgentExecutor", + "list": false + }, + "requests_wrapper": { + "required": true, + "placeholder": "", + "show": true, + "multiline": false, + "password": false, + "name": "requests_wrapper", + "type": "RequestsWrapper", + "list": false + }, + "_type": "OpenAPIToolkit" + }, + "description": "Toolkit for interacting with a OpenAPI api.", + "base_classes": [ + "BaseToolkit" + ] + }, + "id": "dndnode_32", + "value": null + }, + "selected": false, + "positionAbsolute": { + "x": 745.308873444751, + "y": -37.007911201107675 + }, + "dragging": false + }, + { + "width": 384, + "height": 351, + "id": "dndnode_33", + "type": "genericNode", + "position": { + "x": 281.30887344475104, + "y": 2.9920887988923255 + }, + "data": { + "type": "JsonAgent", + "node": { + "template": { + "toolkit": { + "required": true, + "placeholder": "", + "show": true, + "multiline": false, + "password": false, + "name": "toolkit", + "type": "BaseToolkit", + "list": false + }, + "llm": { + "required": true, + "placeholder": "", + "show": true, + "multiline": false, + "password": false, + "name": "llm", + "type": "BaseLanguageModel", + "list": false + }, + "_type": "JsonAgent" + }, + "description": "Construct a json agent from an LLM and tools.", + "base_classes": [ + "AgentExecutor" + ] + }, + "id": "dndnode_33", + "value": null + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": 281.30887344475104, + "y": 2.9920887988923255 + } + }, + { + "width": 384, + "height": 349, + "id": "dndnode_34", + "type": "genericNode", + "position": { + "x": 301.30887344475104, + "y": 532.9920887988924 + }, + "data": { + "type": "RequestsWrapper", + "node": { + "template": { + "headers": { + "required": false, + "placeholder": "", + "show": true, + "multiline": true, + "value": "{'Authorization':\n 'Bearer '}", + "password": false, + "name": "headers", + "type": "code", + "list": false + }, + "aiosession": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "password": false, + "name": "aiosession", + "type": "ClientSession", + "list": false + }, + "_type": "RequestsWrapper" + }, + "description": "Lightweight wrapper around requests library.", + "base_classes": [ + "RequestsWrapper" + ] + }, + "id": "dndnode_34", + "value": null + }, + "positionAbsolute": { + "x": 301.30887344475104, + "y": 532.9920887988924 + } + }, + { + "width": 384, + "height": 407, + "id": "dndnode_35", + "type": "genericNode", + "position": { + "x": -754.691126555249, + "y": -37.00791120110762 + }, + "data": { + "type": "JsonSpec", + "node": { + "template": { + "dict_": { + "required": true, + "placeholder": "", + "show": true, + "multiline": false, + "value": "api-with-examples.yaml", + "suffixes": [ + ".json", + ".yaml", + ".yml" + ], + "password": false, + "name": "dict_", + "type": "file", + "list": false, + "fileTypes": [ + "json", + "yaml", + "yml" + ], + "content": "data:application/x-yaml;base64,b3BlbmFwaTogIjMuMC4wIgppbmZvOgogIHRpdGxlOiBTaW1wbGUgQVBJIG92ZXJ2aWV3CiAgdmVyc2lvbjogMi4wLjAKcGF0aHM6CiAgLzoKICAgIGdldDoKICAgICAgb3BlcmF0aW9uSWQ6IGxpc3RWZXJzaW9uc3YyCiAgICAgIHN1bW1hcnk6IExpc3QgQVBJIHZlcnNpb25zCiAgICAgIHJlc3BvbnNlczoKICAgICAgICAnMjAwJzoKICAgICAgICAgIGRlc2NyaXB0aW9uOiB8LQogICAgICAgICAgICAyMDAgcmVzcG9uc2UKICAgICAgICAgIGNvbnRlbnQ6CiAgICAgICAgICAgIGFwcGxpY2F0aW9uL2pzb246CiAgICAgICAgICAgICAgZXhhbXBsZXM6IAogICAgICAgICAgICAgICAgZm9vOgogICAgICAgICAgICAgICAgICB2YWx1ZToKICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAidmVyc2lvbnMiOiBbCiAgICAgICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGF0dXMiOiAiQ1VSUkVOVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAidXBkYXRlZCI6ICIyMDExLTAxLTIxVDExOjMzOjIxWiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAidjIuMCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGlua3MiOiBbCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHJlZiI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjIvIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJlbCI6ICJzZWxmIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgInN0YXR1cyI6ICJFWFBFUklNRU5UQUwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgInVwZGF0ZWQiOiAiMjAxMy0wNy0yM1QxMTozMzoyMVoiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogInYzLjAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImxpbmtzIjogWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImhyZWYiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YzLyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJyZWwiOiAic2VsZiIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgJzMwMCc6CiAgICAgICAgICBkZXNjcmlwdGlvbjogfC0KICAgICAgICAgICAgMzAwIHJlc3BvbnNlCiAgICAgICAgICBjb250ZW50OgogICAgICAgICAgICBhcHBsaWNhdGlvbi9qc29uOiAKICAgICAgICAgICAgICBleGFtcGxlczogCiAgICAgICAgICAgICAgICBmb286CiAgICAgICAgICAgICAgICAgIHZhbHVlOiB8CiAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInZlcnNpb25zIjogWwogICAgICAgICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzdGF0dXMiOiAiQ1VSUkVOVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAidXBkYXRlZCI6ICIyMDExLTAxLTIxVDExOjMzOjIxWiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaWQiOiAidjIuMCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAibGlua3MiOiBbCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHJlZiI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjIvIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJlbCI6ICJzZWxmIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgInN0YXR1cyI6ICJFWFBFUklNRU5UQUwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgInVwZGF0ZWQiOiAiMjAxMy0wNy0yM1QxMTozMzoyMVoiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogInYzLjAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImxpbmtzIjogWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImhyZWYiOiAiaHR0cDovLzEyNy4wLjAuMTo4Nzc0L3YzLyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJyZWwiOiAic2VsZiIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICAgICB9CiAgL3YyOgogICAgZ2V0OgogICAgICBvcGVyYXRpb25JZDogZ2V0VmVyc2lvbkRldGFpbHN2MgogICAgICBzdW1tYXJ5OiBTaG93IEFQSSB2ZXJzaW9uIGRldGFpbHMKICAgICAgcmVzcG9uc2VzOgogICAgICAgICcyMDAnOgogICAgICAgICAgZGVzY3JpcHRpb246IHwtCiAgICAgICAgICAgIDIwMCByZXNwb25zZQogICAgICAgICAgY29udGVudDoKICAgICAgICAgICAgYXBwbGljYXRpb24vanNvbjogCiAgICAgICAgICAgICAgZXhhbXBsZXM6CiAgICAgICAgICAgICAgICBmb286CiAgICAgICAgICAgICAgICAgIHZhbHVlOgogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogewogICAgICAgICAgICAgICAgICAgICAgICAic3RhdHVzIjogIkNVUlJFTlQiLAogICAgICAgICAgICAgICAgICAgICAgICAidXBkYXRlZCI6ICIyMDExLTAxLTIxVDExOjMzOjIxWiIsCiAgICAgICAgICAgICAgICAgICAgICAgICJtZWRpYS10eXBlcyI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJiYXNlIjogImFwcGxpY2F0aW9uL3htbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vcGVuc3RhY2suY29tcHV0ZSt4bWw7dmVyc2lvbj0yIgogICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYmFzZSI6ICJhcHBsaWNhdGlvbi9qc29uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9wZW5zdGFjay5jb21wdXRlK2pzb247dmVyc2lvbj0yIgogICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogInYyLjAiLAogICAgICAgICAgICAgICAgICAgICAgICAibGlua3MiOiBbCiAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHJlZiI6ICJodHRwOi8vMTI3LjAuMC4xOjg3NzQvdjIvIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJlbCI6ICJzZWxmIgogICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHJlZiI6ICJodHRwOi8vZG9jcy5vcGVuc3RhY2sub3JnL2FwaS9vcGVuc3RhY2stY29tcHV0ZS8yL29zLWNvbXB1dGUtZGV2Z3VpZGUtMi5wZGYiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJhcHBsaWNhdGlvbi9wZGYiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicmVsIjogImRlc2NyaWJlZGJ5IgogICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHJlZiI6ICJodHRwOi8vZG9jcy5vcGVuc3RhY2sub3JnL2FwaS9vcGVuc3RhY2stY29tcHV0ZS8yL3dhZGwvb3MtY29tcHV0ZS0yLndhZGwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJhcHBsaWNhdGlvbi92bmQuc3VuLndhZGwreG1sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJlbCI6ICJkZXNjcmliZWRieSIKICAgICAgICAgICAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJocmVmIjogImh0dHA6Ly9kb2NzLm9wZW5zdGFjay5vcmcvYXBpL29wZW5zdGFjay1jb21wdXRlLzIvd2FkbC9vcy1jb21wdXRlLTIud2FkbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAidHlwZSI6ICJhcHBsaWNhdGlvbi92bmQuc3VuLndhZGwreG1sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJyZWwiOiAiZGVzY3JpYmVkYnkiCiAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICcyMDMnOgogICAgICAgICAgZGVzY3JpcHRpb246IHwtCiAgICAgICAgICAgIDIwMyByZXNwb25zZQogICAgICAgICAgY29udGVudDoKICAgICAgICAgICAgYXBwbGljYXRpb24vanNvbjogCiAgICAgICAgICAgICAgZXhhbXBsZXM6CiAgICAgICAgICAgICAgICBmb286CiAgICAgICAgICAgICAgICAgIHZhbHVlOgogICAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAgICJ2ZXJzaW9uIjogewogICAgICAgICAgICAgICAgICAgICAgICAic3RhdHVzIjogIkNVUlJFTlQiLAogICAgICAgICAgICAgICAgICAgICAgICAidXBkYXRlZCI6ICIyMDExLTAxLTIxVDExOjMzOjIxWiIsCiAgICAgICAgICAgICAgICAgICAgICAgICJtZWRpYS10eXBlcyI6IFsKICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJiYXNlIjogImFwcGxpY2F0aW9uL3htbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogImFwcGxpY2F0aW9uL3ZuZC5vcGVuc3RhY2suY29tcHV0ZSt4bWw7dmVyc2lvbj0yIgogICAgICAgICAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYmFzZSI6ICJhcHBsaWNhdGlvbi9qc29uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInR5cGUiOiAiYXBwbGljYXRpb24vdm5kLm9wZW5zdGFjay5jb21wdXRlK2pzb247dmVyc2lvbj0yIgogICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgICAgICAgImlkIjogInYyLjAiLAogICAgICAgICAgICAgICAgICAgICAgICAibGlua3MiOiBbCiAgICAgICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaHJlZiI6ICJodHRwOi8vMjMuMjUzLjIyOC4yMTE6ODc3NC92Mi8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicmVsIjogInNlbGYiCiAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJocmVmIjogImh0dHA6Ly9kb2NzLm9wZW5zdGFjay5vcmcvYXBpL29wZW5zdGFjay1jb21wdXRlLzIvb3MtY29tcHV0ZS1kZXZndWlkZS0yLnBkZiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogImFwcGxpY2F0aW9uL3BkZiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJyZWwiOiAiZGVzY3JpYmVkYnkiCiAgICAgICAgICAgICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJocmVmIjogImh0dHA6Ly9kb2NzLm9wZW5zdGFjay5vcmcvYXBpL29wZW5zdGFjay1jb21wdXRlLzIvd2FkbC9vcy1jb21wdXRlLTIud2FkbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0eXBlIjogImFwcGxpY2F0aW9uL3ZuZC5zdW4ud2FkbCt4bWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicmVsIjogImRlc2NyaWJlZGJ5IgogICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIH0K" + }, + "max_value_length": { + "required": false, + "placeholder": "", + "show": true, + "multiline": false, + "value": "4000", + "password": false, + "name": "max_value_length", + "type": "int", + "list": false + }, + "_type": "JsonSpec" + }, + "description": "", + "base_classes": [ + "Tool", + "JsonSpec" + ] + }, + "id": "dndnode_35", + "value": null + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": -754.691126555249, + "y": -37.00791120110762 + } + }, + { + "width": 384, + "height": 563, + "id": "dndnode_36", + "type": "genericNode", + "position": { + "x": -310.69112655524896, + "y": 514.9920887988924 + }, + "data": { + "type": "OpenAIChat", + "node": { + "template": { + "cache": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "password": false, + "name": "cache", + "type": "bool", + "list": false + }, + "verbose": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "value": false, + "password": false, + "name": "verbose", + "type": "bool", + "list": false + }, + "client": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "password": false, + "name": "client", + "type": "Any", + "list": false + }, + "model_name": { + "required": false, + "placeholder": "", + "show": true, + "multiline": false, + "value": "gpt-3.5-turbo", + "password": false, + "name": "model_name", + "type": "str", + "list": false + }, + "model_kwargs": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "password": false, + "name": "model_kwargs", + "type": "code", + "list": false + }, + "openai_api_key": { + "required": false, + "placeholder": "", + "show": true, + "multiline": false, + "password": false, + "name": "openai_api_key", + "type": "str", + "list": false, + "value": "sk-" + }, + "max_retries": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "value": 6, + "password": false, + "name": "max_retries", + "type": "int", + "list": false + }, + "prefix_messages": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "password": false, + "name": "prefix_messages", + "type": "Any", + "list": true + }, + "streaming": { + "required": false, + "placeholder": "", + "show": false, + "multiline": false, + "value": false, + "password": false, + "name": "streaming", + "type": "bool", + "list": false + }, + "_type": "OpenAIChat" + }, + "description": "Wrapper around OpenAI Chat large language models.To use, you should have the ``openai`` python package installed, and theenvironment variable ``OPENAI_API_KEY`` set with your API key.Any parameters that are valid to be passed to the openai.create call can be passedin, even if not explicitly saved on this class.", + "base_classes": [ + "BaseLanguageModel", + "BaseLLM" + ] + }, + "id": "dndnode_36", + "value": null + }, + "selected": false, + "dragging": false, + "positionAbsolute": { + "x": -310.69112655524896, + "y": 514.9920887988924 + } + } + ], + "edges": [ + { + "source": "dndnode_19", + "sourceHandle": "JsonToolkit|dndnode_19|BaseToolkit", + "target": "dndnode_33", + "targetHandle": "BaseToolkit|toolkit|dndnode_33", + "className": "animate-pulse", + "id": "reactflow__edge-dndnode_19JsonToolkit|dndnode_19|BaseToolkit-dndnode_33BaseToolkit|toolkit|dndnode_33", + "selected": false + }, + { + "source": "dndnode_33", + "sourceHandle": "JsonAgent|dndnode_33|AgentExecutor", + "target": "dndnode_32", + "targetHandle": "AgentExecutor|json_agent|dndnode_32", + "className": "animate-pulse", + "id": "reactflow__edge-dndnode_33JsonAgent|dndnode_33|AgentExecutor-dndnode_32AgentExecutor|json_agent|dndnode_32", + "selected": false + }, + { + "source": "dndnode_34", + "sourceHandle": "RequestsWrapper|dndnode_34|RequestsWrapper", + "target": "dndnode_32", + "targetHandle": "RequestsWrapper|requests_wrapper|dndnode_32", + "className": "animate-pulse", + "id": "reactflow__edge-dndnode_34RequestsWrapper|dndnode_34|RequestsWrapper-dndnode_32RequestsWrapper|requests_wrapper|dndnode_32", + "selected": false + }, + { + "source": "dndnode_35", + "sourceHandle": "JsonSpec|dndnode_35|Tool|JsonSpec", + "target": "dndnode_19", + "targetHandle": "JsonSpec|spec|dndnode_19", + "className": "animate-pulse", + "id": "reactflow__edge-dndnode_35JsonSpec|dndnode_35|Tool|JsonSpec-dndnode_19JsonSpec|spec|dndnode_19", + "selected": false + }, + { + "source": "dndnode_36", + "sourceHandle": "OpenAIChat|dndnode_36|BaseLanguageModel|BaseLLM", + "target": "dndnode_33", + "targetHandle": "BaseLanguageModel|llm|dndnode_33", + "className": "animate-pulse", + "id": "reactflow__edge-dndnode_36OpenAIChat|dndnode_36|BaseLanguageModel|BaseLLM-dndnode_33BaseLanguageModel|llm|dndnode_33" + } + ], + "viewport": { + "x": 0, + "y": 0, + "zoom": 1 + } + }, + "chat": [ + { + "message": "test", + "isSend": true + } + ] +} \ No newline at end of file diff --git a/tests/test_graph.py b/tests/test_graph.py index 6dbe8a7e3..9bc7b4220 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -1,16 +1,36 @@ import json +from langflow.graph.nodes import ( + WrapperNode, + AgentNode, + ToolNode, + ChainNode, + PromptNode, + LLMNode, + ToolkitNode, + FileToolNode, +) import pytest from langchain.agents import AgentExecutor from langflow.graph import Edge, Graph, Node from langflow.utils.payload import build_json, get_root_node + # Test cases for the graph module +# now we have three types of graph: +# BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH -def get_graph(basic=True): + +def get_graph(_type="basic"): """Get a graph from a json file""" - path = pytest.BASIC_EXAMPLE_PATH if basic else pytest.COMPLEX_EXAMPLE_PATH + if _type == "basic": + path = pytest.BASIC_EXAMPLE_PATH + elif _type == "complex": + path = pytest.COMPLEX_EXAMPLE_PATH + elif _type == "openapi": + path = pytest.OPENAPI_EXAMPLE_PATH + with open(path, "r") as f: flow_graph = json.load(f) data_graph = flow_graph["data"] @@ -19,26 +39,94 @@ def get_graph(basic=True): return Graph(nodes, edges) -def test_get_nodes_with_target(): +@pytest.fixture +def basic_graph(): + return get_graph() + + +@pytest.fixture +def complex_graph(): + return get_graph("complex") + + +@pytest.fixture +def openapi_graph(): + return get_graph("openapi") + + +def get_node_by_type(graph, node_type): + """Get a node by type""" + return next((node for node in graph.nodes if isinstance(node, node_type)), None) + + +def test_graph_structure(basic_graph): + assert isinstance(basic_graph, Graph) + assert len(basic_graph.nodes) > 0 + assert len(basic_graph.edges) > 0 + for node in basic_graph.nodes: + assert isinstance(node, Node) + for edge in basic_graph.edges: + assert isinstance(edge, Edge) + assert edge.source in basic_graph.nodes + assert edge.target in basic_graph.nodes + + +def test_circular_dependencies(basic_graph): + assert isinstance(basic_graph, Graph) + + def check_circular(node, visited): + visited.add(node) + neighbors = basic_graph.get_nodes_with_target(node) + for neighbor in neighbors: + if neighbor in visited: + return True + if check_circular(neighbor, visited.copy()): + return True + return False + + for node in basic_graph.nodes: + assert not check_circular(node, set()) + + +def test_invalid_node_types(): + graph_data = { + "nodes": [ + { + "id": "1", + "data": { + "node": { + "base_classes": ["BaseClass"], + "template": { + "_type": "InvalidNodeType", + }, + }, + }, + }, + ], + "edges": [], + } + with pytest.raises(Exception): + Graph(graph_data["nodes"], graph_data["edges"]) + + +def test_get_nodes_with_target(basic_graph): """Test getting connected nodes""" - graph = get_graph() - assert isinstance(graph, Graph) + assert isinstance(basic_graph, Graph) # Get root node - root = get_root_node(graph) + root = get_root_node(basic_graph) assert root is not None - connected_nodes = graph.get_nodes_with_target(root) + connected_nodes = basic_graph.get_nodes_with_target(root) assert connected_nodes is not None -def test_get_node_neighbors_basic(): +def test_get_node_neighbors_basic(basic_graph): """Test getting node neighbors""" - graph = get_graph(basic=True) - assert isinstance(graph, Graph) + assert isinstance(basic_graph, Graph) # Get root node - root = get_root_node(graph) + root = get_root_node(basic_graph) assert root is not None - neighbors = graph.get_node_neighbors(root) + neighbors = basic_graph.get_node_neighbors(root) assert neighbors is not None assert isinstance(neighbors, dict) # Root Node is an Agent, it requires an LLMChain and tools @@ -57,7 +145,7 @@ def test_get_node_neighbors_basic(): for neighbor, val in neighbors.items() if "Chain" in neighbor.data["type"] and val ) - chain_neighbors = graph.get_node_neighbors(chain) + chain_neighbors = basic_graph.get_node_neighbors(chain) assert chain_neighbors is not None assert isinstance(chain_neighbors, dict) # Check if there is a LLM in the chain's neighbors @@ -74,15 +162,13 @@ def test_get_node_neighbors_basic(): ) -def test_get_node_neighbors_complex(): +def test_get_node_neighbors_complex(complex_graph): """Test getting node neighbors""" - - graph = get_graph(basic=False) - assert isinstance(graph, Graph) + assert isinstance(complex_graph, Graph) # Get root node - root = get_root_node(graph) + root = get_root_node(complex_graph) assert root is not None - neighbors = graph.get_nodes_with_target(root) + neighbors = complex_graph.get_nodes_with_target(root) assert neighbors is not None # Neighbors should be a list of nodes assert isinstance(neighbors, list) @@ -93,7 +179,7 @@ def test_get_node_neighbors_complex(): assert any("Tool" in neighbor.data["type"] for neighbor in neighbors) # Now on to the Chain's neighbors chain = next(neighbor for neighbor in neighbors if "Chain" in neighbor.data["type"]) - chain_neighbors = graph.get_nodes_with_target(chain) + chain_neighbors = complex_graph.get_nodes_with_target(chain) assert chain_neighbors is not None # Check if there is a LLM in the chain's neighbors assert any("OpenAI" in neighbor.data["type"] for neighbor in chain_neighbors) @@ -101,7 +187,7 @@ def test_get_node_neighbors_complex(): assert any("Prompt" in neighbor.data["type"] for neighbor in chain_neighbors) # Now on to the Tool's neighbors tool = next(neighbor for neighbor in neighbors if "Tool" in neighbor.data["type"]) - tool_neighbors = graph.get_nodes_with_target(tool) + tool_neighbors = complex_graph.get_nodes_with_target(tool) assert tool_neighbors is not None # Check if there is an Agent in the tool's neighbors assert any("Agent" in neighbor.data["type"] for neighbor in tool_neighbors) @@ -109,7 +195,7 @@ def test_get_node_neighbors_complex(): agent = next( neighbor for neighbor in tool_neighbors if "Agent" in neighbor.data["type"] ) - agent_neighbors = graph.get_nodes_with_target(agent) + agent_neighbors = complex_graph.get_nodes_with_target(agent) assert agent_neighbors is not None # Check if there is a Tool in the agent's neighbors assert any("Tool" in neighbor.data["type"] for neighbor in agent_neighbors) @@ -117,62 +203,57 @@ def test_get_node_neighbors_complex(): tool = next( neighbor for neighbor in agent_neighbors if "Tool" in neighbor.data["type"] ) - tool_neighbors = graph.get_nodes_with_target(tool) + tool_neighbors = complex_graph.get_nodes_with_target(tool) assert tool_neighbors is not None # Check if there is a PythonFunction in the tool's neighbors assert any("PythonFunction" in neighbor.data["type"] for neighbor in tool_neighbors) -def test_get_node(): +def test_get_node(basic_graph): """Test getting a single node""" - graph = get_graph() - node_id = graph.nodes[0].id - node = graph.get_node(node_id) + node_id = basic_graph.nodes[0].id + node = basic_graph.get_node(node_id) assert isinstance(node, Node) assert node.id == node_id -def test_build_nodes(): +def test_build_nodes(basic_graph): """Test building nodes""" - graph = get_graph() - assert len(graph.nodes) == len(graph._nodes) - for node in graph.nodes: + + assert len(basic_graph.nodes) == len(basic_graph._nodes) + for node in basic_graph.nodes: assert isinstance(node, Node) -def test_build_edges(): +def test_build_edges(basic_graph): """Test building edges""" - graph = get_graph() - assert len(graph.edges) == len(graph._edges) - for edge in graph.edges: + assert len(basic_graph.edges) == len(basic_graph._edges) + for edge in basic_graph.edges: assert isinstance(edge, Edge) assert isinstance(edge.source, Node) assert isinstance(edge.target, Node) -def test_get_root_node(): +def test_get_root_node(basic_graph, complex_graph): """Test getting root node""" - graph = get_graph(basic=True) - assert isinstance(graph, Graph) - root = get_root_node(graph) + assert isinstance(basic_graph, Graph) + root = get_root_node(basic_graph) assert root is not None assert isinstance(root, Node) assert root.data["type"] == "ZeroShotAgent" # For complex example, the root node is a ZeroShotAgent too - graph = get_graph(basic=False) - assert isinstance(graph, Graph) - root = get_root_node(graph) + assert isinstance(complex_graph, Graph) + root = get_root_node(complex_graph) assert root is not None assert isinstance(root, Node) assert root.data["type"] == "ZeroShotAgent" -def test_build_json(): +def test_build_json(basic_graph): """Test building JSON from graph""" - graph = get_graph() - assert isinstance(graph, Graph) - root = get_root_node(graph) - json_data = build_json(root, graph) + assert isinstance(basic_graph, Graph) + root = get_root_node(basic_graph) + json_data = build_json(root, basic_graph) assert isinstance(json_data, dict) assert json_data["_type"] == "zero-shot-react-description" assert isinstance(json_data["llm_chain"], dict) @@ -188,38 +269,37 @@ def test_build_json(): assert all(isinstance(val, str) for val in json_data["return_values"]) -def test_validate_edges(): +def test_validate_edges(basic_graph): """Test validating edges""" - graph = get_graph() - assert isinstance(graph, Graph) + + assert isinstance(basic_graph, Graph) # all edges should be valid - assert all(edge.valid for edge in graph.edges) + assert all(edge.valid for edge in basic_graph.edges) -def test_matched_type(): +def test_matched_type(basic_graph): """Test matched type attribute in Edge""" - graph = get_graph() - assert isinstance(graph, Graph) + assert isinstance(basic_graph, Graph) # all edges should be valid - assert all(edge.valid for edge in graph.edges) + assert all(edge.valid for edge in basic_graph.edges) # all edges should have a matched_type attribute - assert all(hasattr(edge, "matched_type") for edge in graph.edges) + assert all(hasattr(edge, "matched_type") for edge in basic_graph.edges) # The matched_type attribute should be in the source_types attr - assert all(edge.matched_type in edge.source_types for edge in graph.edges) + assert all(edge.matched_type in edge.source_types for edge in basic_graph.edges) -def test_build_params(): +def test_build_params(basic_graph): """Test building params""" - graph = get_graph() - assert isinstance(graph, Graph) + + assert isinstance(basic_graph, Graph) # all edges should be valid - assert all(edge.valid for edge in graph.edges) + assert all(edge.valid for edge in basic_graph.edges) # all edges should have a matched_type attribute - assert all(hasattr(edge, "matched_type") for edge in graph.edges) + assert all(hasattr(edge, "matched_type") for edge in basic_graph.edges) # The matched_type attribute should be in the source_types attr - assert all(edge.matched_type in edge.source_types for edge in graph.edges) + assert all(edge.matched_type in edge.source_types for edge in basic_graph.edges) # Get the root node - root = get_root_node(graph) + root = get_root_node(basic_graph) # Root node is a ZeroShotAgent # which requires an llm_chain, allowed_tools and return_values assert isinstance(root.params, dict) @@ -261,7 +341,7 @@ def test_build_params(): assert isinstance(llm_node.params["model_name"], str) -def test_build(): +def test_build(basic_graph, complex_graph): """Test Node's build method""" # def build(self): # # The params dict is used to build the module @@ -284,18 +364,81 @@ def test_build(): # # and instantiate it with the params # # and return the instance # return LANGCHAIN_TYPES_DICT[self.node_type](**self.params) - graph = get_graph() - assert isinstance(graph, Graph) + + assert isinstance(basic_graph, Graph) # Now we test the build method # Build the Agent - agent = graph.build() + agent = basic_graph.build() # The agent should be a AgentExecutor assert isinstance(agent, AgentExecutor) # Now we test the complex example - graph = get_graph(basic=False) - assert isinstance(graph, Graph) + assert isinstance(complex_graph, Graph) # Now we test the build method - agent = graph.build() + agent = complex_graph.build() # The agent should be a AgentExecutor assert isinstance(agent, AgentExecutor) + + +def test_agent_node_build(basic_graph): + agent_node = get_node_by_type(basic_graph, AgentNode) + assert agent_node is not None + built_object = agent_node.build() + assert built_object is not None + # Add any further assertions specific to the AgentNode's build() method + + +def test_tool_node_build(basic_graph): + tool_node = get_node_by_type(basic_graph, ToolNode) + assert tool_node is not None + built_object = tool_node.build() + assert built_object is not None + # Add any further assertions specific to the ToolNode's build() method + + +def test_chain_node_build(complex_graph): + chain_node = get_node_by_type(complex_graph, ChainNode) + assert chain_node is not None + built_object = chain_node.build() + assert built_object is not None + # Add any further assertions specific to the ChainNode's build() method + + +def test_prompt_node_build(complex_graph): + prompt_node = get_node_by_type(complex_graph, PromptNode) + assert prompt_node is not None + built_object = prompt_node.build() + assert built_object is not None + # Add any further assertions specific to the PromptNode's build() method + + +def test_llm_node_build(basic_graph): + llm_node = get_node_by_type(basic_graph, LLMNode) + assert llm_node is not None + built_object = llm_node.build() + assert built_object is not None + # Add any further assertions specific to the LLMNode's build() method + + +def test_toolkit_node_build(openapi_graph): + toolkit_node = get_node_by_type(openapi_graph, ToolkitNode) + assert toolkit_node is not None + built_object = toolkit_node.build() + assert built_object is not None + # Add any further assertions specific to the ToolkitNode's build() method + + +def test_file_tool_node_build(openapi_graph): + file_tool_node = get_node_by_type(openapi_graph, FileToolNode) + assert file_tool_node is not None + built_object = file_tool_node.build() + assert built_object is not None + # Add any further assertions specific to the FileToolNode's build() method + + +def test_wrapper_node_build(openapi_graph): + wrapper_node = get_node_by_type(openapi_graph, WrapperNode) + assert wrapper_node is not None + built_object = wrapper_node.build() + assert built_object is not None + # Add any further assertions specific to the WrapperNode's build() method