@@ -132,55 +132,82 @@ def get_today_usage():
132132 return daily_usage # daily_usage is (premium_tokens, mini_tokens) or None
133133
134134# get model family and build payload
135- def is_gpt4_family_model (model : str ) -> bool :
135+ def model_supports_temperature (model : str ) -> bool :
136136 """
137137 Harry rule:
138- Only gpt-4* models get legacy Chat Completions sampling/function payloads.
139- Examples allowed:
140- gpt-4
141- gpt-4.1
142- gpt-4o
143- gpt-4-turbo
144-
145- Everything else:
146- no temperature
147- no old functions/function_call payload
138+ Only gpt-4* models get temperature.
139+ GPT-5.x / other newer reasoning-ish models do NOT get temperature.
148140 """
149141 model = (model or "" ).strip ().lower ()
150142 return model .startswith ("gpt-4" )
151143
144+
145+ def build_openai_tools (function_specs ):
146+ """
147+ Convert legacy ChatKeke custom_functions format:
148+
149+ {"name": "...", "description": "...", "parameters": {...}}
150+
151+ into modern Chat Completions tools format:
152+
153+ {"type": "function", "function": {...}}
154+
155+ If already in tools format, keep it.
156+ """
157+ tools = []
158+
159+ for item in function_specs or []:
160+ if isinstance (item , dict ) and item .get ("type" ) == "function" and "function" in item :
161+ tools .append (item )
162+ else :
163+ tools .append ({
164+ "type" : "function" ,
165+ "function" : item
166+ })
167+
168+ return tools
169+
170+
152171def build_chat_payload (bot , messages , * , include_functions = True , max_tokens = None ):
153172 """
154173 Centralized Chat Completions payload builder.
155174
156175 Important:
157- - Only gpt -4* models receive temperature.
158- - Only gpt-4* models receive old-style functions/function_call .
159- - Non-4 models become plain text Chat Completions calls .
176+ - GPT -4* gets temperature.
177+ - Non-GPT-4 models do NOT get temperature .
178+ - Function calling uses modern tools/tool_choice for all models .
160179 """
161180 payload = {
162181 "model" : bot .model ,
163182 "messages" : messages ,
164183 }
165184
166185 if max_tokens is not None :
167- if is_gpt4_family_model (bot .model ):
186+ if model_supports_temperature (bot .model ):
168187 payload ["max_tokens" ] = max_tokens
169188 else :
170189 payload ["max_completion_tokens" ] = max_tokens
171190
172- if is_gpt4_family_model (bot .model ):
191+ if model_supports_temperature (bot .model ):
173192 payload ["temperature" ] = bot .temperature
174-
175- if include_functions :
176- payload ["functions" ] = custom_functions
177- payload ["function_call" ] = "auto"
178193 else :
179194 bot .logger .info (
180- "Model %s is not gpt-4*; omitting temperature and legacy function_call/functions ." ,
195+ "Model %s is not gpt-4*; omitting temperature." ,
181196 bot .model
182197 )
183198
199+ if include_functions :
200+ tools = build_openai_tools (custom_functions )
201+ if tools :
202+ payload ["tools" ] = tools
203+ payload ["tool_choice" ] = "auto"
204+ if include_functions :
205+ tools = build_openai_tools (custom_functions )
206+ if tools :
207+ payload ["tools" ] = tools
208+ payload ["tool_choice" ] = "auto"
209+ payload ["parallel_tool_calls" ] = False
210+
184211 return payload
185212
186213def extract_chat_reply_or_raise (response_json ):
@@ -204,6 +231,51 @@ def extract_chat_reply_or_raise(response_json):
204231 content = message .get ("content" ) or ""
205232 return content .strip ()
206233
234+ def extract_function_call_or_none (response_json ):
235+ """
236+ Supports both:
237+ - modern Chat Completions tool_calls
238+ - old legacy function_call
239+
240+ Returns:
241+ {"name": str, "arguments": str, "tool_call_id": str|None}
242+ or None.
243+ """
244+ if not isinstance (response_json , dict ):
245+ raise RuntimeError (f"OpenAI response was not a JSON object: { response_json !r} " )
246+
247+ if "error" in response_json :
248+ raise RuntimeError (f"OpenAI API error: { response_json ['error' ]} " )
249+
250+ choices = response_json .get ("choices" ) or []
251+ if not choices :
252+ raise RuntimeError (f"OpenAI response had empty choices: { response_json } " )
253+
254+ message_obj = choices [0 ].get ("message" ) or {}
255+
256+ # Modern tools/tool_calls path.
257+ tool_calls = message_obj .get ("tool_calls" ) or []
258+ if tool_calls :
259+ first_tool_call = tool_calls [0 ]
260+ if first_tool_call .get ("type" ) == "function" :
261+ fn = first_tool_call .get ("function" ) or {}
262+ return {
263+ "name" : fn .get ("name" ),
264+ "arguments" : fn .get ("arguments" ) or "{}" ,
265+ "tool_call_id" : first_tool_call .get ("id" ),
266+ }
267+
268+ # Legacy functions/function_call path.
269+ legacy_function_call = message_obj .get ("function_call" )
270+ if legacy_function_call :
271+ return {
272+ "name" : legacy_function_call .get ("name" ),
273+ "arguments" : legacy_function_call .get ("arguments" ) or "{}" ,
274+ "tool_call_id" : None ,
275+ }
276+
277+ return None
278+
207279# model picker auto-switch
208280def pick_model_auto_switch (bot ):
209281 if not config_auto .has_section ('ModelAutoSwitch' ):
@@ -327,10 +399,9 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
327399 bot .logger .info (f"[Debug] Total token usage from file: { bot .total_token_usage } " )
328400
329401 except ValueError :
330- # Handle the case where the value in config.ini is not a valid integer
331402 bot .logger .error ("Invalid value for GlobalMaxTokenUsagePerDay in the configuration file." )
332403 await update .message .reply_text ("An error occurred while processing your request: couldn't get proper token count. Please try again later." )
333- # max_tokens_config = 0 # Assign a default value (0 or any other appropriate default)
404+ return
334405
335406 # Safely compare user_token_count and max_tokens_config
336407 if not is_no_limit and (bot .total_token_usage + user_token_count ) > max_tokens_config :
@@ -657,15 +728,10 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
657728 # > function calling
658729 # ~~~~~~~~~~~~~~~~~~
659730
660- # Check for a 'function_call' in the response
661- choices = response_json .get ("choices" ) or []
662- if not choices :
663- raise RuntimeError (f"OpenAI response had empty choices: { response_json } " )
731+ # Check for a modern tool_call or legacy function_call in the response
732+ function_call = extract_function_call_or_none (response_json )
664733
665- message_obj = choices [0 ].get ("message" ) or {}
666-
667- if "function_call" in message_obj :
668- function_call = message_obj ["function_call" ]
734+ if function_call :
669735 function_name = function_call ["name" ]
670736
671737 # ~~~~~~~~~~~~~~~~~~~~~~
@@ -720,7 +786,12 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
720786 bot .logger .info (f"Updated chat history with calculator result: { chat_history } " )
721787
722788 # Make an API request using the updated chat history
723- response_json = await make_api_request (bot , chat_history , bot .timeout )
789+ response_json = await make_api_request (
790+ bot ,
791+ chat_history ,
792+ bot .timeout ,
793+ include_functions = False
794+ )
724795
725796 # Extract and handle the content from the API response
726797 bot_reply_content = extract_chat_reply_or_raise (response_json )
@@ -786,7 +857,12 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
786857 context .chat_data ['chat_history' ] = chat_history
787858
788859 # Prepare the payload for the API request with updated chat history
789- response_json = await make_api_request (bot , chat_history , bot .timeout )
860+ response_json = await make_api_request (
861+ bot ,
862+ chat_history ,
863+ bot .timeout ,
864+ include_functions = False
865+ )
790866
791867 # Log the API request payload
792868 bot .logger .info (f"API Request Payload: { payload } " )
@@ -795,9 +871,7 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
795871 bot_reply_content = extract_chat_reply_or_raise (response_json )
796872
797873 # Only call strip if bot_reply_content is not None
798- bot_reply = "" # Default value if no content is found or an empty response is received
799- if bot_reply_content :
800- bot_reply = bot_reply_content .strip ()
874+ bot_reply = bot_reply_content .strip () if bot_reply_content else "🤔"
801875
802876 # Count tokens in the bot's response
803877 bot_token_count = bot .count_tokens (bot_reply )
@@ -874,7 +948,12 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
874948 bot .logger .info (f"Updated chat history: { chat_history } " )
875949
876950 # Make an API request using the updated chat history
877- response_json = await make_api_request (bot , chat_history , bot .timeout )
951+ response_json = await make_api_request (
952+ bot ,
953+ chat_history ,
954+ bot .timeout ,
955+ include_functions = False
956+ )
878957
879958 # Extract and handle the content from the API response
880959 bot_reply_content = extract_chat_reply_or_raise (response_json )
@@ -952,7 +1031,12 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
9521031 context .chat_data ['chat_history' ] = chat_history
9531032
9541033 # Make an API request using the updated chat history
955- response_json = await make_api_request (bot , chat_history , bot .timeout )
1034+ response_json = await make_api_request (
1035+ bot ,
1036+ chat_history ,
1037+ bot .timeout ,
1038+ include_functions = False
1039+ )
9561040
9571041 # Extract and handle the content from the API response
9581042 bot_reply_content = extract_chat_reply_or_raise (response_json )
@@ -1036,7 +1120,12 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
10361120 context .chat_data ['chat_history' ] = chat_history
10371121
10381122 # Make the API request using the new function
1039- response_json = await make_api_request (bot , chat_history , bot .timeout )
1123+ response_json = await make_api_request (
1124+ bot ,
1125+ chat_history ,
1126+ bot .timeout ,
1127+ include_functions = False
1128+ )
10401129
10411130 # Log the API request payload
10421131 bot .logger .info (f"API Request Payload: { payload } " )
@@ -1045,9 +1134,7 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
10451134 bot_reply_content = extract_chat_reply_or_raise (response_json )
10461135
10471136 # Only call strip if bot_reply_content is not None
1048- bot_reply = "" # Default value if no content is found or an empty response is received
1049- if bot_reply_content :
1050- bot_reply = bot_reply_content .strip ()
1137+ bot_reply = bot_reply_content .strip () if bot_reply_content else "🤔"
10511138
10521139 # Count tokens in the bot's response
10531140 bot_token_count = bot .count_tokens (bot_reply )
@@ -1233,7 +1320,12 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
12331320 bot .logger .info (f"Updated chat history: { chat_history } " )
12341321
12351322 # Make an API request using the updated chat history
1236- response_json = await make_api_request (bot , chat_history , bot .timeout )
1323+ response_json = await make_api_request (
1324+ bot ,
1325+ chat_history ,
1326+ bot .timeout ,
1327+ include_functions = False
1328+ )
12371329
12381330 # Extract and handle the content from the API response
12391331 bot_reply_content = extract_chat_reply_or_raise (response_json )
@@ -1367,7 +1459,12 @@ async def handle_message(bot, update: Update, context: CallbackContext, logger)
13671459
13681460 # (D) Re-invoke GPT to produce final user-facing text
13691461 context .chat_data ['chat_history' ] = chat_history
1370- response_json = await make_api_request (bot , chat_history , bot .timeout )
1462+ response_json = await make_api_request (
1463+ bot ,
1464+ chat_history ,
1465+ bot .timeout ,
1466+ include_functions = False
1467+ )
13711468
13721469 final_reply_content = extract_chat_reply_or_raise (response_json )
13731470 final_reply = final_reply_content .strip () if final_reply_content else ""
@@ -1656,7 +1753,7 @@ async def generate_response_based_on_updated_context(bot, context, chat_id):
16561753
16571754 # Extract the generated response from the response data.
16581755 try :
1659- generated_response = extract_chat_reply_or_raise (response_data )
1756+ generated_response = extract_chat_reply_or_raise (response_data ) or "🤔"
16601757 except Exception as e :
16611758 logging .error (f"Fallback OpenAI response was invalid: { e } " )
16621759 generated_response = "🤔"
@@ -1707,9 +1804,13 @@ async def make_api_request_with_retry(bot, chat_history, retries=3, timeout=30):
17071804 raise RuntimeError ("All retry attempts failed; no response_json was produced." )
17081805
17091806# API request function module
1710- async def make_api_request (bot , chat_history , timeout = 30 ):
1807+ async def make_api_request (bot , chat_history , timeout = 30 , include_functions = True ):
17111808 # Prepare the payload for the API request with updated chat history
1712- payload = build_chat_payload (bot , chat_history )
1809+ payload = build_chat_payload (
1810+ bot ,
1811+ chat_history ,
1812+ include_functions = include_functions
1813+ )
17131814
17141815 # Make the API request
17151816 headers = {
0 commit comments