camel-ai 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of camel-ai might be problematic. Click here for more details.
- camel/__init__.py +1 -1
- camel/agents/__init__.py +2 -0
- camel/agents/chat_agent.py +40 -53
- camel/agents/knowledge_graph_agent.py +221 -0
- camel/configs/__init__.py +29 -0
- camel/configs/anthropic_config.py +73 -0
- camel/configs/base_config.py +22 -0
- camel/configs/openai_config.py +132 -0
- camel/embeddings/openai_embedding.py +7 -2
- camel/functions/__init__.py +13 -8
- camel/functions/open_api_function.py +380 -0
- camel/functions/open_api_specs/coursera/__init__.py +13 -0
- camel/functions/open_api_specs/coursera/openapi.yaml +82 -0
- camel/functions/open_api_specs/klarna/__init__.py +13 -0
- camel/functions/open_api_specs/klarna/openapi.yaml +87 -0
- camel/functions/open_api_specs/speak/__init__.py +13 -0
- camel/functions/open_api_specs/speak/openapi.yaml +151 -0
- camel/functions/openai_function.py +3 -1
- camel/functions/retrieval_functions.py +61 -0
- camel/functions/slack_functions.py +275 -0
- camel/models/__init__.py +2 -0
- camel/models/anthropic_model.py +16 -2
- camel/models/base_model.py +8 -2
- camel/models/model_factory.py +7 -3
- camel/models/openai_audio_models.py +251 -0
- camel/models/openai_model.py +12 -4
- camel/models/stub_model.py +5 -1
- camel/retrievers/__init__.py +2 -0
- camel/retrievers/auto_retriever.py +47 -36
- camel/retrievers/base.py +42 -37
- camel/retrievers/bm25_retriever.py +10 -19
- camel/retrievers/cohere_rerank_retriever.py +108 -0
- camel/retrievers/vector_retriever.py +43 -26
- camel/storages/vectordb_storages/qdrant.py +3 -1
- camel/toolkits/__init__.py +21 -0
- camel/toolkits/base.py +22 -0
- camel/toolkits/github_toolkit.py +245 -0
- camel/types/__init__.py +6 -0
- camel/types/enums.py +44 -3
- camel/utils/__init__.py +4 -2
- camel/utils/commons.py +97 -173
- {camel_ai-0.1.3.dist-info → camel_ai-0.1.4.dist-info}/METADATA +9 -3
- {camel_ai-0.1.3.dist-info → camel_ai-0.1.4.dist-info}/RECORD +44 -26
- camel/configs.py +0 -271
- {camel_ai-0.1.3.dist-info → camel_ai-0.1.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the “License”);
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an “AS IS” BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import asdict, dataclass, field
|
|
17
|
+
from typing import TYPE_CHECKING, Optional, Sequence
|
|
18
|
+
|
|
19
|
+
from openai._types import NOT_GIVEN, NotGiven
|
|
20
|
+
|
|
21
|
+
from camel.configs.base_config import BaseConfig
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from camel.functions import OpenAIFunction
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class ChatGPTConfig(BaseConfig):
|
|
29
|
+
r"""Defines the parameters for generating chat completions using the
|
|
30
|
+
OpenAI API.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
temperature (float, optional): Sampling temperature to use, between
|
|
34
|
+
:obj:`0` and :obj:`2`. Higher values make the output more random,
|
|
35
|
+
while lower values make it more focused and deterministic.
|
|
36
|
+
(default: :obj:`0.2`)
|
|
37
|
+
top_p (float, optional): An alternative to sampling with temperature,
|
|
38
|
+
called nucleus sampling, where the model considers the results of
|
|
39
|
+
the tokens with top_p probability mass. So :obj:`0.1` means only
|
|
40
|
+
the tokens comprising the top 10% probability mass are considered.
|
|
41
|
+
(default: :obj:`1.0`)
|
|
42
|
+
n (int, optional): How many chat completion choices to generate for
|
|
43
|
+
each input message. (default: :obj:`1`)
|
|
44
|
+
stream (bool, optional): If True, partial message deltas will be sent
|
|
45
|
+
as data-only server-sent events as they become available.
|
|
46
|
+
(default: :obj:`False`)
|
|
47
|
+
stop (str or list, optional): Up to :obj:`4` sequences where the API
|
|
48
|
+
will stop generating further tokens. (default: :obj:`None`)
|
|
49
|
+
max_tokens (int, optional): The maximum number of tokens to generate
|
|
50
|
+
in the chat completion. The total length of input tokens and
|
|
51
|
+
generated tokens is limited by the model's context length.
|
|
52
|
+
(default: :obj:`None`)
|
|
53
|
+
presence_penalty (float, optional): Number between :obj:`-2.0` and
|
|
54
|
+
:obj:`2.0`. Positive values penalize new tokens based on whether
|
|
55
|
+
they appear in the text so far, increasing the model's likelihood
|
|
56
|
+
to talk about new topics. See more information about frequency and
|
|
57
|
+
presence penalties. (default: :obj:`0.0`)
|
|
58
|
+
frequency_penalty (float, optional): Number between :obj:`-2.0` and
|
|
59
|
+
:obj:`2.0`. Positive values penalize new tokens based on their
|
|
60
|
+
existing frequency in the text so far, decreasing the model's
|
|
61
|
+
likelihood to repeat the same line verbatim. See more information
|
|
62
|
+
about frequency and presence penalties. (default: :obj:`0.0`)
|
|
63
|
+
logit_bias (dict, optional): Modify the likelihood of specified tokens
|
|
64
|
+
appearing in the completion. Accepts a json object that maps tokens
|
|
65
|
+
(specified by their token ID in the tokenizer) to an associated
|
|
66
|
+
bias value from :obj:`-100` to :obj:`100`. Mathematically, the bias
|
|
67
|
+
is added to the logits generated by the model prior to sampling.
|
|
68
|
+
The exact effect will vary per model, but values between:obj:` -1`
|
|
69
|
+
and :obj:`1` should decrease or increase likelihood of selection;
|
|
70
|
+
values like :obj:`-100` or :obj:`100` should result in a ban or
|
|
71
|
+
exclusive selection of the relevant token. (default: :obj:`{}`)
|
|
72
|
+
user (str, optional): A unique identifier representing your end-user,
|
|
73
|
+
which can help OpenAI to monitor and detect abuse.
|
|
74
|
+
(default: :obj:`""`)
|
|
75
|
+
tools (list[OpenAIFunction], optional): A list of tools the model may
|
|
76
|
+
call. Currently, only functions are supported as a tool. Use this
|
|
77
|
+
to provide a list of functions the model may generate JSON inputs
|
|
78
|
+
for. A max of 128 functions are supported.
|
|
79
|
+
tool_choice (Union[dict[str, str], str], optional): Controls which (if
|
|
80
|
+
any) tool is called by the model. :obj:`"none"` means the model
|
|
81
|
+
will not call any tool and instead generates a message.
|
|
82
|
+
:obj:`"auto"` means the model can pick between generating a
|
|
83
|
+
message or calling one or more tools. :obj:`"required"` means the
|
|
84
|
+
model must call one or more tools. Specifying a particular tool
|
|
85
|
+
via {"type": "function", "function": {"name": "my_function"}}
|
|
86
|
+
forces the model to call that tool. :obj:`"none"` is the default
|
|
87
|
+
when no tools are present. :obj:`"auto"` is the default if tools
|
|
88
|
+
are present.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
temperature: float = 0.2 # openai default: 1.0
|
|
92
|
+
top_p: float = 1.0
|
|
93
|
+
n: int = 1
|
|
94
|
+
stream: bool = False
|
|
95
|
+
stop: str | Sequence[str] | NotGiven = NOT_GIVEN
|
|
96
|
+
max_tokens: int | NotGiven = NOT_GIVEN
|
|
97
|
+
presence_penalty: float = 0.0
|
|
98
|
+
frequency_penalty: float = 0.0
|
|
99
|
+
logit_bias: dict = field(default_factory=dict)
|
|
100
|
+
user: str = ""
|
|
101
|
+
tools: Optional[list[OpenAIFunction]] = None
|
|
102
|
+
tool_choice: Optional[dict[str, str] | str] = None
|
|
103
|
+
|
|
104
|
+
def __post_init__(self):
|
|
105
|
+
if self.tools is not None:
|
|
106
|
+
object.__setattr__(
|
|
107
|
+
self,
|
|
108
|
+
'tools',
|
|
109
|
+
[tool.get_openai_tool_schema() for tool in self.tools],
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
OPENAI_API_PARAMS = {param for param in asdict(ChatGPTConfig()).keys()}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class OpenSourceConfig(BaseConfig):
|
|
118
|
+
r"""Defines parameters for setting up open-source models and includes
|
|
119
|
+
parameters to be passed to chat completion function of OpenAI API.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
model_path (str): The path to a local folder containing the model
|
|
123
|
+
files or the model card in HuggingFace hub.
|
|
124
|
+
server_url (str): The URL to the server running the model inference
|
|
125
|
+
which will be used as the API base of OpenAI API.
|
|
126
|
+
api_params (ChatGPTConfig): An instance of :obj:ChatGPTConfig to
|
|
127
|
+
contain the arguments to be passed to OpenAI API.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
model_path: str
|
|
131
|
+
server_url: str
|
|
132
|
+
api_params: ChatGPTConfig = field(default_factory=ChatGPTConfig)
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
-
|
|
14
|
+
import os
|
|
15
|
+
from typing import Any, List, Optional
|
|
15
16
|
|
|
16
17
|
from openai import OpenAI
|
|
17
18
|
|
|
@@ -26,6 +27,8 @@ class OpenAIEmbedding(BaseEmbedding[str]):
|
|
|
26
27
|
Args:
|
|
27
28
|
model (OpenAiEmbeddingModel, optional): The model type to be used for
|
|
28
29
|
generating embeddings. (default: :obj:`ModelType.ADA_2`)
|
|
30
|
+
api_key (Optional[str]): The API key for authenticating with the
|
|
31
|
+
OpenAI service. (default: :obj:`None`)
|
|
29
32
|
|
|
30
33
|
Raises:
|
|
31
34
|
RuntimeError: If an unsupported model type is specified.
|
|
@@ -34,12 +37,14 @@ class OpenAIEmbedding(BaseEmbedding[str]):
|
|
|
34
37
|
def __init__(
|
|
35
38
|
self,
|
|
36
39
|
model_type: EmbeddingModelType = EmbeddingModelType.ADA_2,
|
|
40
|
+
api_key: Optional[str] = None,
|
|
37
41
|
) -> None:
|
|
38
42
|
if not model_type.is_openai:
|
|
39
43
|
raise ValueError("Invalid OpenAI embedding model type.")
|
|
40
44
|
self.model_type = model_type
|
|
41
45
|
self.output_dim = model_type.output_dim
|
|
42
|
-
self.
|
|
46
|
+
self._api_key = api_key or os.environ.get("OPENAI_API_KEY")
|
|
47
|
+
self.client = OpenAI(timeout=60, max_retries=3, api_key=self._api_key)
|
|
43
48
|
|
|
44
49
|
@api_key_required
|
|
45
50
|
def embed_list(
|
camel/functions/__init__.py
CHANGED
|
@@ -11,27 +11,32 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
-
|
|
15
|
-
from ..loaders.unstructured_io import UnstructuredIO
|
|
16
|
-
from .google_maps_function import MAP_FUNCS
|
|
17
|
-
from .math_functions import MATH_FUNCS
|
|
14
|
+
# ruff: noqa: I001
|
|
18
15
|
from .openai_function import (
|
|
19
16
|
OpenAIFunction,
|
|
20
17
|
get_openai_function_schema,
|
|
21
18
|
get_openai_tool_schema,
|
|
22
19
|
)
|
|
20
|
+
|
|
21
|
+
from .google_maps_function import MAP_FUNCS
|
|
22
|
+
from .math_functions import MATH_FUNCS
|
|
23
|
+
from .open_api_function import OPENAPI_FUNCS
|
|
24
|
+
from .retrieval_functions import RETRIEVAL_FUNCS
|
|
23
25
|
from .search_functions import SEARCH_FUNCS
|
|
24
26
|
from .twitter_function import TWITTER_FUNCS
|
|
25
27
|
from .weather_functions import WEATHER_FUNCS
|
|
28
|
+
from .slack_functions import SLACK_FUNCS
|
|
26
29
|
|
|
27
30
|
__all__ = [
|
|
28
31
|
'OpenAIFunction',
|
|
29
|
-
'get_openai_tool_schema',
|
|
30
32
|
'get_openai_function_schema',
|
|
33
|
+
'get_openai_tool_schema',
|
|
34
|
+
'MAP_FUNCS',
|
|
31
35
|
'MATH_FUNCS',
|
|
36
|
+
'OPENAPI_FUNCS',
|
|
37
|
+
'RETRIEVAL_FUNCS',
|
|
32
38
|
'SEARCH_FUNCS',
|
|
33
|
-
'WEATHER_FUNCS',
|
|
34
|
-
'MAP_FUNCS',
|
|
35
39
|
'TWITTER_FUNCS',
|
|
36
|
-
'
|
|
40
|
+
'WEATHER_FUNCS',
|
|
41
|
+
'SLACK_FUNCS',
|
|
37
42
|
]
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the “License”);
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an “AS IS” BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
from typing import Any, Callable, Dict, List, Tuple
|
|
17
|
+
|
|
18
|
+
import prance
|
|
19
|
+
import requests
|
|
20
|
+
|
|
21
|
+
from camel.functions import OpenAIFunction
|
|
22
|
+
from camel.types import OpenAPIName
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_openapi_file(openapi_spec_path: str) -> Dict[str, Any]:
|
|
26
|
+
r"""Load and parse an OpenAPI specification file.
|
|
27
|
+
|
|
28
|
+
This function utilizes the `prance.ResolvingParser` to parse and resolve
|
|
29
|
+
the given OpenAPI specification file, returning the parsed OpenAPI
|
|
30
|
+
specification as a dictionary.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
openapi_spec_path (str): The file path or URL to the OpenAPI
|
|
34
|
+
specification.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dict[str, Any]: The parsed OpenAPI specification as a dictionary.
|
|
38
|
+
"""
|
|
39
|
+
# Load the OpenAPI spec
|
|
40
|
+
parser = prance.ResolvingParser(openapi_spec_path)
|
|
41
|
+
openapi_spec = parser.specification
|
|
42
|
+
return openapi_spec
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def openapi_spec_to_openai_schemas(
|
|
46
|
+
api_name: str, openapi_spec: Dict[str, Any]
|
|
47
|
+
) -> List[Dict[str, Any]]:
|
|
48
|
+
r"""Convert OpenAPI specification to OpenAI schema format.
|
|
49
|
+
|
|
50
|
+
This function iterates over the paths and operations defined in an
|
|
51
|
+
OpenAPI specification, filtering out deprecated operations. For each
|
|
52
|
+
operation, it constructs a schema in a format suitable for OpenAI,
|
|
53
|
+
including operation metadata such as function name, description,
|
|
54
|
+
parameters, and request bodies. It raises a ValueError if an operation
|
|
55
|
+
lacks a description or summary.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
api_name (str): The name of the API, used to prefix generated function
|
|
59
|
+
names.
|
|
60
|
+
openapi_spec (Dict[str, Any]): The OpenAPI specification as a
|
|
61
|
+
dictionary.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
List[Dict[str, Any]]: A list of dictionaries, each representing a
|
|
65
|
+
function in the OpenAI schema format, including details about the
|
|
66
|
+
function's name, description, and parameters.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If an operation in the OpenAPI specification does not have
|
|
70
|
+
a description or summary.
|
|
71
|
+
|
|
72
|
+
Note:
|
|
73
|
+
This function assumes that the OpenAPI specification follows the 3.0+
|
|
74
|
+
format.
|
|
75
|
+
|
|
76
|
+
Reference:
|
|
77
|
+
https://swagger.io/specification/
|
|
78
|
+
"""
|
|
79
|
+
result = []
|
|
80
|
+
|
|
81
|
+
for path, path_item in openapi_spec.get('paths', {}).items():
|
|
82
|
+
for method, op in path_item.items():
|
|
83
|
+
if op.get('deprecated') is True:
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
# Get the function name from the operationId
|
|
87
|
+
# or construct it from the API method, and path
|
|
88
|
+
function_name = f"{api_name}"
|
|
89
|
+
operation_id = op.get('operationId')
|
|
90
|
+
if operation_id:
|
|
91
|
+
function_name += f"_{operation_id}"
|
|
92
|
+
else:
|
|
93
|
+
function_name += f"{method}{path.replace('/', '_')}"
|
|
94
|
+
|
|
95
|
+
description = op.get('description') or op.get('summary')
|
|
96
|
+
if not description:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"{method} {path} Operation from {api_name} "
|
|
99
|
+
f"does not have a description or summary."
|
|
100
|
+
)
|
|
101
|
+
description += " " if description[-1] != " " else ""
|
|
102
|
+
description += f"This function is from {api_name} API. "
|
|
103
|
+
|
|
104
|
+
# If the OpenAPI spec has a description,
|
|
105
|
+
# add it to the operation description
|
|
106
|
+
if 'description' in openapi_spec.get('info', {}):
|
|
107
|
+
description += f"{openapi_spec['info']['description']}"
|
|
108
|
+
|
|
109
|
+
# Get the parameters for the operation, if any
|
|
110
|
+
params = op.get('parameters', [])
|
|
111
|
+
properties: Dict[str, Any] = {}
|
|
112
|
+
required = []
|
|
113
|
+
|
|
114
|
+
for param in params:
|
|
115
|
+
if not param.get('deprecated', False):
|
|
116
|
+
param_name = param['name'] + '_in_' + param['in']
|
|
117
|
+
properties[param_name] = {}
|
|
118
|
+
|
|
119
|
+
if 'description' in param:
|
|
120
|
+
properties[param_name]['description'] = param[
|
|
121
|
+
'description'
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
if 'schema' in param:
|
|
125
|
+
if (
|
|
126
|
+
properties[param_name].get('description')
|
|
127
|
+
and 'description' in param['schema']
|
|
128
|
+
):
|
|
129
|
+
param['schema'].pop('description')
|
|
130
|
+
properties[param_name].update(param['schema'])
|
|
131
|
+
|
|
132
|
+
if param.get('required'):
|
|
133
|
+
required.append(param_name)
|
|
134
|
+
|
|
135
|
+
# If the property dictionary does not have a description,
|
|
136
|
+
# use the parameter name as the description
|
|
137
|
+
if 'description' not in properties[param_name]:
|
|
138
|
+
properties[param_name]['description'] = param['name']
|
|
139
|
+
|
|
140
|
+
if 'type' not in properties[param_name]:
|
|
141
|
+
properties[param_name]['type'] = 'Any'
|
|
142
|
+
|
|
143
|
+
# Process requestBody if present
|
|
144
|
+
if 'requestBody' in op:
|
|
145
|
+
properties['requestBody'] = {}
|
|
146
|
+
requestBody = op['requestBody']
|
|
147
|
+
if requestBody.get('required') is True:
|
|
148
|
+
required.append('requestBody')
|
|
149
|
+
|
|
150
|
+
content = requestBody.get('content', {})
|
|
151
|
+
json_content = content.get('application/json', {})
|
|
152
|
+
json_schema = json_content.get('schema', {})
|
|
153
|
+
if json_schema:
|
|
154
|
+
properties['requestBody'] = json_schema
|
|
155
|
+
if 'description' not in properties['requestBody']:
|
|
156
|
+
properties['requestBody']['description'] = (
|
|
157
|
+
"The request body, with parameters specifically "
|
|
158
|
+
"described under the `properties` key"
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
function = {
|
|
162
|
+
"type": "function",
|
|
163
|
+
"function": {
|
|
164
|
+
"name": function_name,
|
|
165
|
+
"description": description,
|
|
166
|
+
"parameters": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"properties": properties,
|
|
169
|
+
"required": required,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
result.append(function)
|
|
174
|
+
|
|
175
|
+
return result # Return the result list
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def openapi_function_decorator(
|
|
179
|
+
base_url: str, path: str, method: str, operation: Dict[str, Any]
|
|
180
|
+
) -> Callable:
|
|
181
|
+
r"""Decorate a function to make HTTP requests based on OpenAPI operation
|
|
182
|
+
details.
|
|
183
|
+
|
|
184
|
+
This decorator takes the base URL, path, HTTP method, and operation details
|
|
185
|
+
from an OpenAPI specification, and returns a decorator. The decorated
|
|
186
|
+
function can then be called with keyword arguments corresponding to the
|
|
187
|
+
operation's parameters. The decorator handles constructing the request URL,
|
|
188
|
+
setting headers, query parameters, and the request body as specified by the
|
|
189
|
+
operation details.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
base_url (str): The base URL for the API.
|
|
193
|
+
path (str): The path for the API endpoint, relative to the base URL.
|
|
194
|
+
method (str): The HTTP method (e.g., 'get', 'post') for the request.
|
|
195
|
+
operation (Dict[str, Any]): A dictionary containing the OpenAPI
|
|
196
|
+
operation details, including parameters and request body
|
|
197
|
+
definitions.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Callable: A decorator that, when applied to a function, enables the
|
|
201
|
+
function to make HTTP requests based on the provided OpenAPI
|
|
202
|
+
operation details.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def inner_decorator(openapi_function: Callable) -> Callable:
|
|
206
|
+
def wrapper(**kwargs):
|
|
207
|
+
request_url = f"{base_url.rstrip('/')}/{path.lstrip('/')}"
|
|
208
|
+
headers = {}
|
|
209
|
+
params = {}
|
|
210
|
+
cookies = {}
|
|
211
|
+
|
|
212
|
+
# Assign parameters to the correct position
|
|
213
|
+
for param in operation.get('parameters', []):
|
|
214
|
+
input_param_name = param['name'] + '_in_' + param['in']
|
|
215
|
+
# Irrelevant arguments does not affect function operation
|
|
216
|
+
if input_param_name in kwargs:
|
|
217
|
+
if param['in'] == 'path':
|
|
218
|
+
request_url = request_url.replace(
|
|
219
|
+
f"{{{param['name']}}}",
|
|
220
|
+
str(kwargs[input_param_name]),
|
|
221
|
+
)
|
|
222
|
+
elif param['in'] == 'query':
|
|
223
|
+
params[param['name']] = kwargs[input_param_name]
|
|
224
|
+
elif param['in'] == 'header':
|
|
225
|
+
headers[param['name']] = kwargs[input_param_name]
|
|
226
|
+
elif param['in'] == 'cookie':
|
|
227
|
+
cookies[param['name']] = kwargs[input_param_name]
|
|
228
|
+
|
|
229
|
+
if 'requestBody' in operation:
|
|
230
|
+
request_body = kwargs.get('requestBody', {})
|
|
231
|
+
content_type_list = list(
|
|
232
|
+
operation.get('requestBody', {}).get('content', {}).keys()
|
|
233
|
+
)
|
|
234
|
+
if content_type_list:
|
|
235
|
+
content_type = content_type_list[0]
|
|
236
|
+
headers.update({"Content-Type": content_type})
|
|
237
|
+
|
|
238
|
+
# send the request body based on the Content-Type
|
|
239
|
+
if content_type == "application/json":
|
|
240
|
+
response = requests.request(
|
|
241
|
+
method.upper(),
|
|
242
|
+
request_url,
|
|
243
|
+
params=params,
|
|
244
|
+
headers=headers,
|
|
245
|
+
cookies=cookies,
|
|
246
|
+
json=request_body,
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Unsupported content type: {content_type}"
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
# If there is no requestBody, no request body is sent
|
|
254
|
+
response = requests.request(
|
|
255
|
+
method.upper(),
|
|
256
|
+
request_url,
|
|
257
|
+
params=params,
|
|
258
|
+
headers=headers,
|
|
259
|
+
cookies=cookies,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
return response.json()
|
|
264
|
+
except json.JSONDecodeError:
|
|
265
|
+
raise ValueError(
|
|
266
|
+
"Response could not be decoded as JSON. "
|
|
267
|
+
"Please check the input parameters."
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return wrapper
|
|
271
|
+
|
|
272
|
+
return inner_decorator
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def generate_openapi_funcs(
|
|
276
|
+
api_name: str, openapi_spec: Dict[str, Any]
|
|
277
|
+
) -> List[Callable]:
|
|
278
|
+
r"""Generates a list of Python functions based on an OpenAPI specification.
|
|
279
|
+
|
|
280
|
+
This function dynamically creates a list of callable functions that
|
|
281
|
+
represent the API operations defined in an OpenAPI specification document.
|
|
282
|
+
Each function is designed to perform an HTTP request corresponding to an
|
|
283
|
+
API operation (e.g., GET, POST) as defined in the specification. The
|
|
284
|
+
functions are decorated with `openapi_function_decorator`, which
|
|
285
|
+
configures them to construct and send the HTTP requests with appropriate
|
|
286
|
+
parameters, headers, and body content.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
api_name (str): The name of the API, used to prefix generated function
|
|
290
|
+
names.
|
|
291
|
+
openapi_spec (Dict[str, Any]): The OpenAPI specification as a
|
|
292
|
+
dictionary.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
List[Callable]: A list containing the generated functions. Each
|
|
296
|
+
function, when called, will make an HTTP request according to its
|
|
297
|
+
corresponding API operation defined in the OpenAPI specification.
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
ValueError: If the OpenAPI specification does not contain server
|
|
301
|
+
information, which is necessary for determining the base URL for
|
|
302
|
+
the API requests.
|
|
303
|
+
"""
|
|
304
|
+
# Check server information
|
|
305
|
+
servers = openapi_spec.get('servers', [])
|
|
306
|
+
if not servers:
|
|
307
|
+
raise ValueError("No server information found in OpenAPI spec.")
|
|
308
|
+
base_url = servers[0].get('url') # Use the first server URL
|
|
309
|
+
|
|
310
|
+
functions = []
|
|
311
|
+
|
|
312
|
+
# Traverse paths and methods
|
|
313
|
+
for path, methods in openapi_spec.get('paths', {}).items():
|
|
314
|
+
for method, operation in methods.items():
|
|
315
|
+
# Get the function name from the operationId
|
|
316
|
+
# or construct it from the API method, and path
|
|
317
|
+
operation_id = operation.get('operationId')
|
|
318
|
+
if operation_id:
|
|
319
|
+
function_name = f"{api_name}_{operation_id}"
|
|
320
|
+
else:
|
|
321
|
+
sanitized_path = path.replace('/', '_').strip('_')
|
|
322
|
+
function_name = f"{api_name}_{method}_{sanitized_path}"
|
|
323
|
+
|
|
324
|
+
@openapi_function_decorator(base_url, path, method, operation)
|
|
325
|
+
def openapi_function(**kwargs):
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
openapi_function.__name__ = function_name
|
|
329
|
+
|
|
330
|
+
functions.append(openapi_function)
|
|
331
|
+
|
|
332
|
+
return functions
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def combine_all_funcs_schemas() -> Tuple[List[Callable], List[Dict[str, Any]]]:
|
|
336
|
+
r"""Combines functions and schemas from multiple OpenAPI specifications.
|
|
337
|
+
|
|
338
|
+
Iterates over a list of OpenAPI specs, parses each, and generates functions
|
|
339
|
+
and schemas based on the defined operations and schemas respectively.
|
|
340
|
+
Assumes specification files are named 'openapi.yaml' located in
|
|
341
|
+
`open_api_specs/<api_name>/`.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
Tuple[List[Callable], List[Dict[str, Any]]]:: one of callable
|
|
345
|
+
functions for API operations, and another of dictionaries
|
|
346
|
+
representing the schemas from the specifications.
|
|
347
|
+
"""
|
|
348
|
+
combined_func_lst = []
|
|
349
|
+
combined_schemas_list = []
|
|
350
|
+
|
|
351
|
+
for api_name in OpenAPIName:
|
|
352
|
+
# Parse the OpenAPI specification for each API
|
|
353
|
+
current_dir = os.path.dirname(__file__)
|
|
354
|
+
spec_file_path = os.path.join(
|
|
355
|
+
current_dir, 'open_api_specs', f'{api_name.value}', 'openapi.yaml'
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
openapi_spec = parse_openapi_file(spec_file_path)
|
|
359
|
+
|
|
360
|
+
# Generate and merge function schemas
|
|
361
|
+
openapi_functions_schemas = openapi_spec_to_openai_schemas(
|
|
362
|
+
api_name.value, openapi_spec
|
|
363
|
+
)
|
|
364
|
+
combined_schemas_list.extend(openapi_functions_schemas)
|
|
365
|
+
|
|
366
|
+
# Generate and merge function lists
|
|
367
|
+
openapi_functions_list = generate_openapi_funcs(
|
|
368
|
+
api_name.value, openapi_spec
|
|
369
|
+
)
|
|
370
|
+
combined_func_lst.extend(openapi_functions_list)
|
|
371
|
+
|
|
372
|
+
return combined_func_lst, combined_schemas_list
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
combined_funcs_lst, combined_schemas_list = combine_all_funcs_schemas()
|
|
376
|
+
|
|
377
|
+
OPENAPI_FUNCS: List[OpenAIFunction] = [
|
|
378
|
+
OpenAIFunction(a_func, a_schema)
|
|
379
|
+
for a_func, a_schema in zip(combined_funcs_lst, combined_schemas_list)
|
|
380
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the “License”);
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an “AS IS” BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
openapi: 3.0.1
|
|
2
|
+
info:
|
|
3
|
+
title: Search API
|
|
4
|
+
version: v1
|
|
5
|
+
description: Find recommendation for courses, specializations, and degrees on Coursera.
|
|
6
|
+
servers:
|
|
7
|
+
- url: https://www.coursera.org
|
|
8
|
+
description: API schema for search APIs exposed to 3rd party services (e.g. OpenAI)
|
|
9
|
+
tags:
|
|
10
|
+
- name: SearchV1Controller
|
|
11
|
+
description: the Search V1 Controller API
|
|
12
|
+
paths:
|
|
13
|
+
/api/rest/v1/search:
|
|
14
|
+
post:
|
|
15
|
+
summary:
|
|
16
|
+
A public API that searches the Coursera catalog for products (e.g. courses) that
|
|
17
|
+
are relevant to the provided query string.
|
|
18
|
+
tags:
|
|
19
|
+
- search-v1-controller
|
|
20
|
+
operationId:
|
|
21
|
+
search
|
|
22
|
+
requestBody:
|
|
23
|
+
content:
|
|
24
|
+
application/json:
|
|
25
|
+
schema:
|
|
26
|
+
$ref: '#/components/schemas/SearchQuery'
|
|
27
|
+
required: true
|
|
28
|
+
responses:
|
|
29
|
+
"200":
|
|
30
|
+
description: OK
|
|
31
|
+
content:
|
|
32
|
+
application/json:
|
|
33
|
+
schema:
|
|
34
|
+
$ref: '#/components/schemas/SearchResponse'
|
|
35
|
+
components:
|
|
36
|
+
schemas:
|
|
37
|
+
SearchQuery:
|
|
38
|
+
type: object
|
|
39
|
+
properties:
|
|
40
|
+
query:
|
|
41
|
+
type: string
|
|
42
|
+
required:
|
|
43
|
+
- query
|
|
44
|
+
example:
|
|
45
|
+
query: machine learning
|
|
46
|
+
SearchResponse:
|
|
47
|
+
properties:
|
|
48
|
+
hits:
|
|
49
|
+
type: array
|
|
50
|
+
items:
|
|
51
|
+
$ref: '#/components/schemas/SearchHit'
|
|
52
|
+
SearchHit:
|
|
53
|
+
type: object
|
|
54
|
+
properties:
|
|
55
|
+
name:
|
|
56
|
+
type: string
|
|
57
|
+
partners:
|
|
58
|
+
type: array
|
|
59
|
+
items:
|
|
60
|
+
type: string
|
|
61
|
+
duration:
|
|
62
|
+
type: string
|
|
63
|
+
partnerLogos:
|
|
64
|
+
type: array
|
|
65
|
+
items:
|
|
66
|
+
type: string
|
|
67
|
+
productDifficultyLevel:
|
|
68
|
+
type: string
|
|
69
|
+
entityType:
|
|
70
|
+
type: string
|
|
71
|
+
avgProductRating:
|
|
72
|
+
type: string
|
|
73
|
+
skills:
|
|
74
|
+
type: string
|
|
75
|
+
imageUrl:
|
|
76
|
+
type: string
|
|
77
|
+
isCourseFree:
|
|
78
|
+
type: string
|
|
79
|
+
isPartOfCourseraPlus:
|
|
80
|
+
type: string
|
|
81
|
+
objectUrl:
|
|
82
|
+
type: string
|