1+ import json
12
2- import inspect
3- import docstring_parser
3+ from mindsdb_sdk . databases import Database
4+ from tenacity import retry , wait_random_exponential , stop_after_attempt
45
56
6- def make_openai_tool (function : callable ):
7+ @retry (wait = wait_random_exponential (multiplier = 1 , max = 40 ), stop = stop_after_attempt (3 ))
8+ def chat_completion_request (client , model , messages , tools = None , tool_choice = None ):
9+ try :
10+ response = client .chat .completions .create (
11+ model = model ,
12+ messages = messages ,
13+ tools = tools ,
14+ tool_choice = tool_choice ,
15+ )
16+ return response
17+ except Exception as e :
18+ print ("Unable to generate ChatCompletion response" )
19+ print (f"Exception: { e } " )
20+ return e
21+
22+
23+ def make_openai_tool (function : callable , description : str = None ) -> dict :
724 """
8- Make an OpenAI tool for a function
25+ Make a generic OpenAI tool for a function
926
1027 :param function: function to generate metadata for
28+ :param description: description of the function
29+
1130 :return: dictionary containing function metadata
1231 """
32+ # You will need to pip install docstring-parser to use this function
33+
34+ import inspect
35+ import docstring_parser
36+
1337 params = inspect .signature (function ).parameters
1438 docstring = docstring_parser .parse (function .__doc__ )
1539
40+ # Get the first line of the docstring as the function description or use the user-provided description
41+ function_description = description or docstring .short_description
42+
1643 function_dict = {
1744 "type" :"function" ,
1845 "function" :{
1946 "name" :function .__name__ ,
20- "description" :docstring . short_description ,
47+ "description" :function_description ,
2148 "parameters" :{
2249 "type" :"object" ,
2350 "properties" :{},
@@ -49,3 +76,124 @@ def make_openai_tool(function: callable):
4976
5077 return function_dict
5178
79+
80+ def make_mindsdb_tool (schema : dict ) -> dict :
81+ """
82+ Make an OpenAI tool for querying a database connection in MindsDB
83+
84+ :param schema: database schema
85+
86+ :return: dictionary containing function metadata for openai tools
87+ """
88+ return {
89+ "type" :"function" ,
90+ "function" :{
91+ "name" :"query_database" ,
92+ "description" :"Use this function to answer user questions. Input should be a fully formed SQL query." ,
93+ "parameters" :{
94+ "type" :"object" ,
95+ "properties" :{
96+ "query" :{
97+ "type" :"string" ,
98+ "description" :f"""
99+ SQL query extracting info to answer the user's question.
100+ SQL should be written using this database schema:
101+ { schema }
102+ The query should be returned in plain text, not in JSON.
103+ """ ,
104+ }
105+ },
106+ "required" :["query" ],
107+ },
108+ }
109+ }
110+
111+
112+ def extract_sql_query (result : str ) -> str :
113+ """
114+ Extract the SQL query from an openai result string
115+
116+ :param result: OpenAI result string
117+ :return: SQL query string
118+ """
119+ # Split the result into lines
120+ lines = result .split ('\n ' )
121+
122+ # Initialize an empty string to hold the query
123+ query = ""
124+
125+ # Initialize a flag to indicate whether we're currently reading the query
126+ reading_query = False
127+
128+ # Iterate over the lines
129+ for line in lines :
130+ # If the line starts with "SQLQuery:", start reading the query
131+ if line .startswith ("SQLQuery:" ):
132+ query = line [len ("SQLQuery:" ):].strip ()
133+ reading_query = True
134+ # If the line starts with "SQLResult:", stop reading the query
135+ elif line .startswith ("SQLResult:" ):
136+ break
137+ # If we're currently reading the query, append the line to the query
138+ elif reading_query :
139+ query += " " + line .strip ()
140+
141+ # If no line starts with "SQLQuery:", return None
142+ if query == "" :
143+ return None
144+
145+ return query
146+
147+
148+ def query_database (database : Database , query : str ) -> str :
149+ """
150+ Execute a query on a database connection
151+
152+ :param database: mindsdb Database object
153+ :param query: SQL query string
154+
155+ :return: query results as a string
156+ """
157+ try :
158+ results = str (
159+ database .query (query ).fetch ()
160+ )
161+ except Exception as e :
162+ results = f"query failed with error: { e } "
163+ return results
164+
165+
166+ def execute_function_call (message , database : Database = None ) -> str :
167+ """
168+ Execute a function call in a message
169+
170+ """
171+ if message .tool_calls [0 ].function .name == "query_database" :
172+ query = json .loads (message .tool_calls [0 ].function .arguments )["query" ]
173+ results = query_database (database , query )
174+ else :
175+ results = f"Error: function { message .tool_calls [0 ].function .name } does not exist"
176+ return results
177+
178+
179+ def pretty_print_conversation (messages ):
180+ # you will need to pip install termcolor
181+ from termcolor import colored
182+ role_to_color = {
183+ "system" :"red" ,
184+ "user" :"green" ,
185+ "assistant" :"blue" ,
186+ "function" :"magenta" ,
187+ }
188+
189+ for message in messages :
190+ if message ["role" ] == "system" :
191+ print (colored (f"system: { message ['content' ]} \n " , role_to_color [message ["role" ]]))
192+ elif message ["role" ] == "user" :
193+ print (colored (f"user: { message ['content' ]} \n " , role_to_color [message ["role" ]]))
194+ elif message ["role" ] == "assistant" and message .get ("function_call" ):
195+ print (colored (f"assistant: { message ['function_call' ]} \n " , role_to_color [message ["role" ]]))
196+ elif message ["role" ] == "assistant" and not message .get ("function_call" ):
197+ print (colored (f"assistant: { message ['content' ]} \n " , role_to_color [message ["role" ]]))
198+ elif message ["role" ] == "function" :
199+ print (colored (f"function ({ message ['name' ]} ): { message ['content' ]} \n " , role_to_color [message ["role" ]]))
0 commit comments