├── .gitignore ├── LICENSE ├── README.md ├── funkagent ├── __init__.py ├── agents.py └── parser.py ├── pyproject.toml ├── quickstart.ipynb └── test ├── __init__.py └── test_parser.py /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 AURELIO - FZCO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FunkAgent 2 | 3 | Get started with: 4 | 5 | ``` 6 | pip install funkagent 7 | ``` 8 | -------------------------------------------------------------------------------- /funkagent/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelio-labs/funkagent/0d7f38a91b4157c2b405262a298b882e382df8a9/funkagent/__init__.py -------------------------------------------------------------------------------- /funkagent/agents.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Optional 3 | from funkagent import parser 4 | 5 | import openai 6 | 7 | sys_msg = """Assistant is a large language model trained by OpenAI. 8 | 9 | Assistant is designed to be able to assist with a wide range of tasks, from answering simple questions to providing in-depth explanations and discussion on a wide range of topics. As a language model, Assistant is able to generate human-like text based on the input it receives, allowing it to engage in natural-sounding conversations and provide responses that are coherent and relevant to the topic at hand. 10 | 11 | Assistant is constantly learning and improving, and its capabilities are constantly evolving. It is able to process and understand large amounts of text, and can use this knowledge to provide accurate and informative responses to a wide range of questions. Additionally, Assistant is able to generate its own text based on the input it receives, allowing it to engage in discussions and provide explanations and descriptions on a wide range of topics. 12 | 13 | Overall, Assistant is a powerful system that can help with a wide range of tasks and provide valuable insights and information on a wide range of topics. Whether you need help with a specific question or just want to have a conversation about a particular topic, Assistant is here to assist. 14 | """ 15 | 16 | 17 | class Agent: 18 | def __init__( 19 | self, 20 | openai_api_key: str, 21 | model_name: str = 'gpt-4-0613', 22 | functions: Optional[list] = None 23 | ): 24 | openai.api_key = openai_api_key 25 | self.model_name = model_name 26 | self.functions = self._parse_functions(functions) 27 | self.func_mapping = self._create_func_mapping(functions) 28 | self.chat_history = [{'role': 'system', 'content': sys_msg}] 29 | 30 | def _parse_functions(self, functions: Optional[list]) -> Optional[list]: 31 | if functions is None: 32 | return None 33 | return [parser.func_to_json(func) for func in functions] 34 | 35 | def _create_func_mapping(self, functions: Optional[list]) -> dict: 36 | if functions is None: 37 | return {} 38 | return {func.__name__: func for func in functions} 39 | 40 | def _create_chat_completion( 41 | self, messages: list, use_functions: bool=True 42 | ) -> openai.ChatCompletion: 43 | if use_functions and self.functions: 44 | res = openai.ChatCompletion.create( 45 | model=self.model_name, 46 | messages=messages, 47 | functions=self.functions 48 | ) 49 | else: 50 | res = openai.ChatCompletion.create( 51 | model=self.model_name, 52 | messages=messages 53 | ) 54 | return res 55 | 56 | def _generate_response(self) -> openai.ChatCompletion: 57 | while True: 58 | print('.', end='') 59 | res = self._create_chat_completion( 60 | self.chat_history + self.internal_thoughts 61 | ) 62 | finish_reason = res.choices[0].finish_reason 63 | 64 | if finish_reason == 'stop' or len(self.internal_thoughts) > 3: 65 | # create the final answer 66 | final_thought = self._final_thought_answer() 67 | final_res = self._create_chat_completion( 68 | self.chat_history + [final_thought], 69 | use_functions=False 70 | ) 71 | return final_res 72 | elif finish_reason == 'function_call': 73 | self._handle_function_call(res) 74 | else: 75 | raise ValueError(f"Unexpected finish reason: {finish_reason}") 76 | 77 | def _handle_function_call(self, res: openai.ChatCompletion): 78 | self.internal_thoughts.append(res.choices[0].message.to_dict()) 79 | func_name = res.choices[0].message.function_call.name 80 | args_str = res.choices[0].message.function_call.arguments 81 | result = self._call_function(func_name, args_str) 82 | res_msg = {'role': 'function', 'name': func_name, 'content': (f"{result}")} 83 | self.internal_thoughts.append(res_msg) 84 | 85 | def _call_function(self, func_name: str, args_str: str): 86 | args = json.loads(args_str) 87 | func = self.func_mapping[func_name] 88 | res = func(**args) 89 | return res 90 | 91 | def _final_thought_answer(self): 92 | thoughts = ("To answer the question I will use these step by step instructions." 93 | "\n\n") 94 | for thought in self.internal_thoughts: 95 | if 'function_call' in thought.keys(): 96 | thoughts += (f"I will use the {thought['function_call']['name']} " 97 | "function to calculate the answer with arguments " 98 | + thought['function_call']['arguments'] + ".\n\n") 99 | else: 100 | thoughts += thought["content"] + "\n\n" 101 | self.final_thought = { 102 | 'role': 'assistant', 103 | 'content': (f"{thoughts} Based on the above, I will now answer the " 104 | "question, this message will only be seen by me so answer with " 105 | "the assumption with that the user has not seen this message.") 106 | } 107 | return self.final_thought 108 | 109 | def ask(self, query: str) -> openai.ChatCompletion: 110 | self.internal_thoughts = [] 111 | self.chat_history.append({'role': 'user', 'content': query}) 112 | res = self._generate_response() 113 | self.chat_history.append(res.choices[0].message.to_dict()) 114 | return res -------------------------------------------------------------------------------- /funkagent/parser.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import inspect 3 | import re 4 | 5 | 6 | def type_mapping(dtype): 7 | if dtype == float: 8 | return "number" 9 | elif dtype == int: 10 | return "integer" 11 | elif dtype == str: 12 | return "string" 13 | else: 14 | return "string" 15 | 16 | 17 | def extract_params(doc_str: str): 18 | # split doc string by newline, skipping empty lines 19 | params_str = [line for line in doc_str.split("\n") if line.strip()] 20 | params = {} 21 | for line in params_str: 22 | # we only look at lines starting with ':param' 23 | if line.strip().startswith(':param'): 24 | param_match = re.findall(r'(?<=:param )\w+', line) 25 | if param_match: 26 | param_name = param_match[0] 27 | desc_match = line.replace(f":param {param_name}:", "").strip() 28 | # if there is a description, store it 29 | if desc_match: 30 | params[param_name] = desc_match 31 | return params 32 | 33 | 34 | def func_to_json(func): 35 | # Check if the function is a functools.partial 36 | if isinstance(func, functools.partial) or isinstance(func, functools.partialmethod): 37 | fixed_args = func.keywords 38 | _func = func.func 39 | if isinstance(func, functools.partial) and (fixed_args is None or fixed_args == {}): 40 | fixed_args = dict(zip(func.func.__code__.co_varnames, func.args)) 41 | else: 42 | fixed_args = {} 43 | _func = func 44 | 45 | # first we get function name 46 | func_name = _func.__name__ 47 | # then we get the function annotations 48 | argspec = inspect.getfullargspec(_func) 49 | # get the function docstring 50 | func_doc = inspect.getdoc(_func) 51 | # parse the docstring to get the description 52 | func_description = ''.join([line for line in func_doc.split("\n") if not line.strip().startswith(':')]) 53 | # parse the docstring to get the descriptions for each parameter in dict format 54 | param_details = extract_params(func_doc) if func_doc else {} 55 | # attach parameter types to params and exclude fixed args 56 | # get params 57 | params = {} 58 | for param_name in argspec.args: 59 | if param_name not in fixed_args.keys(): 60 | params[param_name] = { 61 | "description": param_details.get(param_name) or "", 62 | "type": type_mapping(argspec.annotations.get(param_name, type(None))) 63 | } 64 | # calculate required parameters excluding fixed args 65 | # _required = [arg for arg in argspec.args if arg not in fixed_args] 66 | _required = [i for i in argspec.args if i not in fixed_args.keys()] 67 | if inspect.getfullargspec(_func).defaults: 68 | _required = [argspec.args[i] for i, a in enumerate(argspec.args) if 69 | argspec.args[i] not in inspect.getfullargspec(_func).defaults and argspec.args[ 70 | i] not in fixed_args.keys()] 71 | # then return everything in dict 72 | return { 73 | "name": func_name, 74 | "description": func_description, 75 | "parameters": { 76 | "type": "object", 77 | "properties": params 78 | }, 79 | "required": _required 80 | } 81 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "funkagent" 3 | version = "0.0.3" 4 | description = "Minimal agent framework using OpenAI functions" 5 | authors = ["James Briggs "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | openai = "^0.27.8" 11 | 12 | [tool.poetry.group.test.dependencies] 13 | pytest = "^7.4.0" 14 | 15 | [build-system] 16 | requires = ["poetry-core"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /quickstart.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "!pip install -qU funkagent" 10 | ] 11 | }, 12 | { 13 | "attachments": {}, 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Funkagent Quickstart" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "from funkagent import agents" 27 | ] 28 | }, 29 | { 30 | "attachments": {}, 31 | "cell_type": "markdown", 32 | "metadata": {}, 33 | "source": [ 34 | "First we will create a function to be used by our funkagent. The `Agent` can use multiple functions, in this example we will just use one however." 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "# create a simple function for testing with\n", 44 | "def circumference_calculator(radius: float, something: float = 4.4) -> float:\n", 45 | " \"\"\"Calculates the circumference of a circle given the radius\n", 46 | "\n", 47 | " :param radius: The radius of the circle\n", 48 | " :return: The circumference of the circle\n", 49 | " \"\"\"\n", 50 | " return 2 * 3.14 * radius" 51 | ] 52 | }, 53 | { 54 | "attachments": {}, 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "After this we can initialize an agent using an [OpenAI API key](https://platform.openai.com) and our list of `functions` like so:" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "OPENAI_API_KEY = \"sk-...\"" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": null, 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [ 76 | "agent = agents.Agent(\n", 77 | " openai_api_key=OPENAI_API_KEY,\n", 78 | " functions=[circumference_calculator]\n", 79 | ")" 80 | ] 81 | }, 82 | { 83 | "attachments": {}, 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "We can see the processed function instructions here:" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "agent.functions" 97 | ] 98 | }, 99 | { 100 | "attachments": {}, 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "These instructions will be used by OpenAI GPT-4 / GPT-3.5 to decide whether to use a function, and how to use a chosen function.\n", 105 | "\n", 106 | "We can ask the agent a question using `ask`:" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "agent.ask(\"What is the circumference of a circle with a radius of 5.31?\")" 116 | ] 117 | }, 118 | { 119 | "attachments": {}, 120 | "cell_type": "markdown", 121 | "metadata": {}, 122 | "source": [ 123 | "After asking a question we can see the chat log is added to the internal `chat_history` attribute:" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": {}, 130 | "outputs": [], 131 | "source": [ 132 | "agent.chat_history" 133 | ] 134 | }, 135 | { 136 | "attachments": {}, 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "Because the agent has a *conversational memory* it can refer to past interactions stored within the `chat_history`:" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "agent.ask(\"what is the circumference if we double the radius?\")" 150 | ] 151 | }, 152 | { 153 | "attachments": {}, 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "As before, we can view the `chat_history` and find the last two interactions have been added to the conversational memory:" 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": null, 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "agent.chat_history" 167 | ] 168 | }, 169 | { 170 | "attachments": {}, 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "---" 175 | ] 176 | } 177 | ], 178 | "metadata": { 179 | "kernelspec": { 180 | "display_name": "ml", 181 | "language": "python", 182 | "name": "python3" 183 | }, 184 | "language_info": { 185 | "codemirror_mode": { 186 | "name": "ipython", 187 | "version": 3 188 | }, 189 | "file_extension": ".py", 190 | "mimetype": "text/x-python", 191 | "name": "python", 192 | "nbconvert_exporter": "python", 193 | "pygments_lexer": "ipython3", 194 | "version": "3.9.12" 195 | }, 196 | "orig_nbformat": 4 197 | }, 198 | "nbformat": 4, 199 | "nbformat_minor": 2 200 | } 201 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aurelio-labs/funkagent/0d7f38a91b4157c2b405262a298b882e382df8a9/test/__init__.py -------------------------------------------------------------------------------- /test/test_parser.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import json 3 | 4 | from funkagent.parser import func_to_json 5 | 6 | 7 | def func_with_no_params(): 8 | """ 9 | This function has no parameters 10 | :return: 11 | """ 12 | return 1 13 | 14 | 15 | def func_with_mandatory_params_single_space_doc(a: str, b: str): 16 | """ 17 | This function has mandatory parameters 18 | :param a: 19 | :param b: 20 | :return: 21 | """ 22 | return 1 23 | 24 | 25 | def func_with_optional_params_single_space_doc(a: str, b: str = "b"): 26 | """ 27 | This function has optional parameters 28 | :param a: 29 | :param b: 30 | :return: 31 | """ 32 | return 1 33 | 34 | 35 | def func_with_mandatory_params_double_space_doc(a: str, b: str): 36 | """ 37 | This function has mandatory parameters 38 | 39 | :param a: 40 | :param b: 41 | :return: 42 | """ 43 | return 1 44 | 45 | 46 | def func_with_optional_params_double_space_doc(a: str, b: str = "b"): 47 | """ 48 | This function has optional parameters 49 | 50 | :param a: 51 | :param b: 52 | :return: 53 | """ 54 | return 1 55 | 56 | 57 | def test_func_to_json_func_with_no_params(): 58 | """ 59 | This function tests func_to_json with a function that has no parameters 60 | :return: 61 | """ 62 | _json_fun = func_to_json(func_with_no_params) 63 | assert _json_fun["name"] == "func_with_no_params" 64 | assert _json_fun["description"] == "This function has no parameters" 65 | assert 'properties' in _json_fun["parameters"] 66 | assert 'type' in _json_fun["parameters"] 67 | assert _json_fun["parameters"]["type"] == "object" 68 | assert _json_fun["parameters"]["properties"] == {} 69 | assert _json_fun["required"] == [] 70 | 71 | 72 | def test_func_to_json_func_with_mandatory_params_single_space_doc(): 73 | """ 74 | This function tests func_to_json with a function that has mandatory parameters and single space doc 75 | :return: 76 | """ 77 | _json_fun = func_to_json(func_with_mandatory_params_single_space_doc) 78 | assert _json_fun["name"] == "func_with_mandatory_params_single_space_doc" 79 | assert _json_fun["description"] == "This function has mandatory parameters" 80 | assert 'properties' in _json_fun["parameters"] 81 | assert 'type' in _json_fun["parameters"] 82 | assert _json_fun["parameters"]["type"] == "object" 83 | assert _json_fun["parameters"]["properties"] == { 84 | "a": { 85 | "description": "", 86 | "type": "string" 87 | }, 88 | "b": { 89 | "description": "", 90 | "type": "string" 91 | } 92 | } 93 | assert _json_fun["required"] == ["a", "b"] 94 | 95 | 96 | def test_func_to_json_partial_func_with_mandatory_params_single_space_doc(): 97 | """ 98 | This function tests func_to_json with a function that has mandatory parameters and single space doc 99 | :return: 100 | """ 101 | _json_fun = func_to_json(functools.partial(func_with_mandatory_params_single_space_doc, a="a")) 102 | assert _json_fun["name"] == "func_with_mandatory_params_single_space_doc" 103 | assert _json_fun["description"] == "This function has mandatory parameters" 104 | assert 'properties' in _json_fun["parameters"] 105 | assert 'type' in _json_fun["parameters"] 106 | assert _json_fun["parameters"]["type"] == "object" 107 | assert _json_fun["parameters"]["properties"] == { 108 | "b": { 109 | "description": "", 110 | "type": "string" 111 | } 112 | } 113 | assert _json_fun["required"] == ["b"] 114 | 115 | 116 | def test_func_to_json_partialmethod_func_with_mandatory_params_single_space_doc(): 117 | """ 118 | This function tests func_to_json with a function that has mandatory parameters and single space doc 119 | :return: 120 | """ 121 | _json_fun = func_to_json(functools.partialmethod(func_with_mandatory_params_single_space_doc, b="b")) 122 | assert _json_fun["name"] == "func_with_mandatory_params_single_space_doc" 123 | assert _json_fun["description"] == "This function has mandatory parameters" 124 | assert 'properties' in _json_fun["parameters"] 125 | assert 'type' in _json_fun["parameters"] 126 | assert _json_fun["parameters"]["type"] == "object" 127 | assert _json_fun["parameters"]["properties"] == { 128 | "a": { 129 | "description": "", 130 | "type": "string" 131 | } 132 | } 133 | assert _json_fun["required"] == ["a"] 134 | 135 | 136 | def test_func_to_json_func_with_optional_params_single_space_doc(): 137 | """ 138 | This function tests func_to_json with a function that has optional parameters and single space doc 139 | :return: 140 | """ 141 | _json_fun = func_to_json(func_with_optional_params_single_space_doc) 142 | assert _json_fun["name"] == "func_with_optional_params_single_space_doc" 143 | assert _json_fun["description"] == "This function has optional parameters" 144 | assert 'properties' in _json_fun["parameters"] 145 | assert 'type' in _json_fun["parameters"] 146 | assert _json_fun["parameters"]["type"] == "object" 147 | assert _json_fun["parameters"]["properties"] == { 148 | "a": { 149 | "description": "", 150 | "type": "string" 151 | }, 152 | "b": { 153 | "description": "", 154 | "type": "string" 155 | } 156 | } 157 | assert _json_fun["required"] == ["a"] 158 | 159 | 160 | def test_func_to_json_partial_func_with_optional_params_single_space_doc(): 161 | """ 162 | This function tests func_to_json with a function that has optional parameters and single space doc 163 | :return: 164 | """ 165 | _json_fun = func_to_json(functools.partial(func_with_optional_params_single_space_doc, a="a")) 166 | print(_json_fun) 167 | assert _json_fun["name"] == "func_with_optional_params_single_space_doc" 168 | assert _json_fun["description"] == "This function has optional parameters" 169 | assert 'properties' in _json_fun["parameters"] 170 | assert 'type' in _json_fun["parameters"] 171 | assert _json_fun["parameters"]["type"] == "object" 172 | assert _json_fun["parameters"]["properties"] == { 173 | "b": { 174 | "description": "", 175 | "type": "string" 176 | } 177 | } 178 | assert _json_fun["required"] == [] 179 | 180 | 181 | def test_func_to_json_partial_func_with_optional_params_single_space_doc_positional(): 182 | """ 183 | This function tests func_to_json with a function that has optional parameters and single space doc 184 | :return: 185 | """ 186 | _json_fun = func_to_json(functools.partial(func_with_optional_params_single_space_doc, "a")) 187 | print(_json_fun) 188 | assert _json_fun["name"] == "func_with_optional_params_single_space_doc" 189 | assert _json_fun["description"] == "This function has optional parameters" 190 | assert 'properties' in _json_fun["parameters"] 191 | assert 'type' in _json_fun["parameters"] 192 | assert _json_fun["parameters"]["type"] == "object" 193 | assert _json_fun["parameters"]["properties"] == { 194 | "b": { 195 | "description": "", 196 | "type": "string" 197 | } 198 | } 199 | assert _json_fun["required"] == [] 200 | 201 | 202 | def test_func_to_json_partialmethod_func_with_optional_params_single_space_doc(): 203 | """ 204 | This function tests func_to_json with a function that has optional parameters and single space doc 205 | :return: 206 | """ 207 | _json_fun = func_to_json(functools.partialmethod(func_with_optional_params_single_space_doc, b="b")) 208 | assert _json_fun["name"] == "func_with_optional_params_single_space_doc" 209 | assert _json_fun["description"] == "This function has optional parameters" 210 | assert 'properties' in _json_fun["parameters"] 211 | assert 'type' in _json_fun["parameters"] 212 | assert _json_fun["parameters"]["type"] == "object" 213 | assert _json_fun["parameters"]["properties"] == { 214 | "a": { 215 | "description": "", 216 | "type": "string" 217 | } 218 | } 219 | assert _json_fun["required"] == ['a'] 220 | 221 | 222 | def test_func_to_json_func_with_mandatory_params_double_space_doc(): 223 | """ 224 | This function tests func_to_json with a function that has mandatory parameters and double space doc 225 | :return: 226 | """ 227 | _json_fun = func_to_json(func_with_mandatory_params_double_space_doc) 228 | assert _json_fun["name"] == "func_with_mandatory_params_double_space_doc" 229 | assert _json_fun["description"] == "This function has mandatory parameters" 230 | assert 'properties' in _json_fun["parameters"] 231 | assert 'type' in _json_fun["parameters"] 232 | assert _json_fun["parameters"]["type"] == "object" 233 | assert _json_fun["parameters"]["properties"] == { 234 | "a": { 235 | "description": "", 236 | "type": "string" 237 | }, 238 | "b": { 239 | "description": "", 240 | "type": "string" 241 | } 242 | } 243 | assert _json_fun["required"] == ["a", "b"] 244 | 245 | 246 | def test_func_to_json_func_with_optional_params_double_space_doc(): 247 | """ 248 | This function tests func_to_json with a function that has optional parameters and double space doc 249 | :return: 250 | """ 251 | _json_fun = func_to_json(func_with_optional_params_double_space_doc) 252 | assert _json_fun["name"] == "func_with_optional_params_double_space_doc" 253 | assert _json_fun["description"] == "This function has optional parameters" 254 | assert 'properties' in _json_fun["parameters"] 255 | assert 'type' in _json_fun["parameters"] 256 | assert _json_fun["parameters"]["type"] == "object" 257 | assert _json_fun["parameters"]["properties"] == { 258 | "a": { 259 | "description": "", 260 | "type": "string" 261 | }, 262 | "b": { 263 | "description": "", 264 | "type": "string" 265 | } 266 | } 267 | assert _json_fun["required"] == ["a"] 268 | --------------------------------------------------------------------------------