owenkaplinsky commited on
Commit
ce77b83
·
1 Parent(s): beb3ed2

Convert to responses API

Browse files
Files changed (1) hide show
  1. project/chat.py +155 -139
project/chat.py CHANGED
@@ -720,20 +720,17 @@ List of blocks:
720
  You can create new blocks in the workspace by specifying the block type and its input parameters, if it has any.
721
  You cannot create a MCP block or edit its inputs or outputs.
722
  There are two kinds of nesting in Blockly:
723
- 1. **Statement-level nesting (main-level blocks)**
724
- These are blocks that represent actions or structures, such as loops or conditionals, which can contain other blocks *under* them.
725
- To create this kind of nesting, use **two separate `create_block` commands**:
726
- - First, create the outer block (for example, a `repeat` or `if` block).
727
- - Then, create the inner block *under* it using the `under` parameter.
728
- Example: putting an `if` block inside a `repeat` block.
729
-
730
- 2. **Value-level nesting (output blocks)**
731
- These are blocks that produce a value (like a number, text, or expression). They can’t exist alone in the workspace - they must
732
- be nested inside another block’s input. To create these, you can nest them directly in a single command, for example:
733
-
734
- math_arithmetic(inputs(A: math_number(inputs(NUM: 1)), B: math_number(inputs(NUM: 1))))
735
-
736
- Here, the two `math_number` blocks are nested inside the `math_arithmetic` block in one call.
737
 
738
  When creating blocks, you are never allowed to insert raw text or numbers directly into a block's inputs.
739
  Every value must be enclosed inside the correct block type that represents that value.
@@ -766,6 +763,10 @@ When creating blocks, you are unable to put an outputting block inside of anothe
766
  which already exists. If you are trying to nest input blocks, you must create them all
767
  in one call.
768
 
 
 
 
 
769
  ### Variables
770
 
771
  You will be given the current variables that are in the workspace. Like the blocks, you will see:
@@ -789,87 +790,77 @@ The deployed Space will be public and shareable with others.
789
  tools = [
790
  {
791
  "type": "function",
792
- "function": {
793
- "name": "delete_block",
794
- "description": "Delete a single block using its ID.",
795
- "parameters": {
796
- "type": "object",
797
- "properties": {
798
- "id": {
799
- "type": "string",
800
- "description": "The ID of the block you're trying to delete.",
801
- },
802
  },
803
- "required": ["id"],
804
  },
 
805
  }
806
  },
807
  {
808
  "type": "function",
809
- "function": {
810
- "name": "create_block",
811
- "description": "Creates a single block that allows recursive nested blocks.",
812
- "parameters": {
813
- "type": "object",
814
- "properties": {
815
- "command": {
816
- "type": "string",
817
- "description": "The create block command using the custom DSL format.",
818
- },
819
- "under": {
820
- "type": "string",
821
- "description": "The ID of the block that you want to place this under.",
822
- },
823
  },
824
- "required": ["command"],
825
  },
 
826
  }
827
  },
828
  {
829
  "type": "function",
830
- "function": {
831
- "name": "create_variable",
832
- "description": "Creates a variable.",
833
- "parameters": {
834
- "type": "object",
835
- "properties": {
836
- "name": {
837
- "type": "string",
838
- "description": "The name of the variable you want to create.",
839
- },
840
  },
841
- "required": ["name"],
842
  },
 
843
  }
844
  },
845
  {
846
  "type": "function",
847
- "function": {
848
- "name": "run_mcp",
849
- "description": "Runs the MCP with the given inputs. Create one parameter for each input that the user-created MCP allows.",
850
- "parameters": {
851
- "type": "object",
852
- "properties": {},
853
- "required": [],
854
- "additionalProperties": True
855
- },
856
  }
857
  },
858
  {
859
  "type": "function",
860
- "function": {
861
- "name": "deploy_to_huggingface",
862
- "description": "Deploy the generated MCP tool to a Hugging Face Space. Requires a Hugging Face API key to be set.",
863
- "parameters": {
864
- "type": "object",
865
- "properties": {
866
- "space_name": {
867
- "type": "string",
868
- "description": "The name of the Hugging Face Space to create (e.g., 'my-tool')",
869
- },
870
  },
871
- "required": ["space_name"],
872
  },
 
873
  }
874
  },
875
  ]
@@ -878,7 +869,7 @@ The deployed Space will be public and shareable with others.
878
  # Check if API key is set and create/update client
879
  global client, stored_api_key
880
 
881
- # Use stored key or check environment
882
  api_key = stored_api_key or os.environ.get("OPENAI_API_KEY")
883
 
884
  if api_key and (not client or (hasattr(client, 'api_key') and client.api_key != api_key)):
@@ -892,76 +883,101 @@ The deployed Space will be public and shareable with others.
892
  yield "OpenAI API key not configured. Please set it in File > Settings in the Blockly interface."
893
  return
894
 
895
- # Get the chat context from the global variable
896
  global latest_blockly_chat_code
897
  context = latest_blockly_chat_code
898
  global latest_blockly_vars
899
  vars = latest_blockly_vars
900
 
901
  # Convert history to OpenAI format
902
- full_history = []
903
  for human, ai in history:
904
- full_history.append({"role": "user", "content": human})
905
- full_history.append({"role": "assistant", "content": ai})
906
-
907
- # Debug: Print context to see what we're getting
908
  print(f"[DEBUG] Context received: {context if context else 'No context available'}")
909
-
910
- # Combine system prompt with context
911
- full_system_prompt = SYSTEM_PROMPT
912
  if context:
913
- full_system_prompt += f"\n\nCurrent Blockly workspace state:\n{context}"
914
  else:
915
- full_system_prompt += "\n\nNote: No Blockly workspace context is currently available."
916
-
917
  if vars != "":
918
- full_system_prompt += f"\n\nCurrent Blockly variables:\n{vars}"
919
  else:
920
- full_system_prompt += "\n\nNote: No Blockly variables are currently available."
921
-
922
- # Allow up to 10 consecutive messages from the agent
923
  accumulated_response = ""
924
  max_iterations = 10
925
  current_iteration = 0
926
 
927
- # Start with the user's original message
928
  current_prompt = message
929
- temp_history = full_history.copy()
930
 
 
931
  while current_iteration < max_iterations:
932
  current_iteration += 1
933
 
934
  try:
935
- # Create the completion request with tools
936
- response = client.chat.completions.create(
937
- model="gpt-4o-2024-08-06",
938
- messages=[
939
- {"role": "system", "content": full_system_prompt},
940
- *temp_history,
941
- {"role": "user", "content": current_prompt}
942
- ],
943
  tools=tools,
944
- tool_choice="auto" # Let the model decide whether to use tools
945
  )
946
 
947
- response_message = response.choices[0].message
948
- ai_response = response_message.content or ""
949
 
950
- # Check if the model wants to use tools
951
- if response_message.tool_calls:
952
- # Display the AI's message before executing tools (if any)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
  if ai_response:
954
  if accumulated_response:
955
  accumulated_response += "\n\n"
956
  accumulated_response += ai_response
957
  yield accumulated_response
958
-
959
- # Process each tool call
960
- for tool_call in response_message.tool_calls:
961
- function_name = tool_call.function.name
962
- function_args = json.loads(tool_call.function.arguments)
 
 
 
 
 
 
 
 
 
 
 
963
 
964
- # Execute the appropriate function
965
  tool_result = None
966
  result_label = ""
967
 
@@ -970,29 +986,26 @@ The deployed Space will be public and shareable with others.
970
  print(Fore.YELLOW + f"Agent deleted block with ID `{block_id}`." + Style.RESET_ALL)
971
  tool_result = delete_block(block_id)
972
  result_label = "Delete Operation"
973
-
974
  elif function_name == "create_block":
975
  command = function_args.get("command", "")
976
  under_block_id = function_args.get("under", None)
977
- if under_block_id == None:
978
  print(Fore.YELLOW + f"Agent created block with command `{command}`." + Style.RESET_ALL)
979
  else:
980
- print(Fore.YELLOW + f"Agent created block with command: `{command}`, under block ID: `{under_block_id}`." + Style.RESET_ALL)
981
  tool_result = create_block(command, under_block_id)
982
  result_label = "Create Operation"
983
-
984
  elif function_name == "create_variable":
985
  name = function_args.get("name", "")
986
  print(Fore.YELLOW + f"Agent created variable with name `{name}`." + Style.RESET_ALL)
987
  tool_result = create_variable(name)
988
  result_label = "Create Var Operation"
989
-
990
  elif function_name == "run_mcp":
991
- # Build the MCP call string from the arguments
992
- # run_mcp receives dynamic arguments based on the MCP's inputs
993
  params = []
994
  for key, value in function_args.items():
995
- # Format as key=value for execute_mcp
996
  params.append(f"{key}=\"{value}\"")
997
  mcp_call = f"create_mcp({', '.join(params)})"
998
  print(Fore.YELLOW + f"Agent ran MCP with inputs: {mcp_call}." + Style.RESET_ALL)
@@ -1001,38 +1014,40 @@ The deployed Space will be public and shareable with others.
1001
 
1002
  elif function_name == "deploy_to_huggingface":
1003
  space_name = function_args.get("space_name", "")
1004
- print(Fore.YELLOW + f"Agent deploying to Hugging Face Space: `{space_name}`." + Style.RESET_ALL)
1005
  tool_result = deploy_to_huggingface(space_name)
1006
  result_label = "Deployment Result"
1007
 
1008
- if tool_result:
 
1009
  print(Fore.YELLOW + f"[TOOL RESULT] {tool_result}" + Style.RESET_ALL)
1010
 
1011
- # Yield the tool result
1012
  if accumulated_response:
1013
  accumulated_response += "\n\n"
1014
  accumulated_response += f"**{result_label}:** {tool_result}"
1015
  yield accumulated_response
1016
-
1017
- # Update history with the tool call and result
1018
- temp_history.append({"role": "user", "content": current_prompt})
1019
- temp_history.append({"role": "assistant", "content": ai_response, "tool_calls": response_message.tool_calls})
1020
- temp_history.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(tool_result)})
1021
-
1022
- # Set up next prompt to have the model respond to the tool result
1023
- current_prompt = f"The tool has been executed with the result shown above. Please respond appropriately to the user based on this result."
1024
 
1025
- # Continue to next iteration if tools were used
1026
- continue
1027
 
 
 
1028
  else:
1029
- # No tool calls, this is a regular response
1030
- if accumulated_response:
1031
- accumulated_response += "\n\n"
1032
- accumulated_response += ai_response
 
1033
  yield accumulated_response
1034
  break
1035
-
1036
  except Exception as e:
1037
  if accumulated_response:
1038
  yield f"{accumulated_response}\n\nError in iteration {current_iteration}: {str(e)}"
@@ -1040,12 +1055,13 @@ The deployed Space will be public and shareable with others.
1040
  yield f"Error: {str(e)}"
1041
  return
1042
 
1043
- # If we hit max iterations, add a note
1044
  if current_iteration >= max_iterations:
1045
  accumulated_response += f"\n\n*(Reached maximum of {max_iterations} consecutive responses)*"
1046
  yield accumulated_response
1047
-
1048
- # Create the standard ChatInterface
 
1049
  demo = gr.ChatInterface(
1050
  fn=chat_with_context,
1051
  title="AI Assistant",
 
720
  You can create new blocks in the workspace by specifying the block type and its input parameters, if it has any.
721
  You cannot create a MCP block or edit its inputs or outputs.
722
  There are two kinds of nesting in Blockly:
723
+ 1. **Statement-level nesting (main-level blocks)**
724
+ These are blocks that represent actions or structures, such as loops or conditionals, which can contain other blocks *under* them.
725
+ To create this kind of nesting, use **two separate `create_block` commands**:
726
+ - First, create the outer block (for example, a `repeat` or `if` block).
727
+ - Then, create the inner block *under* it using the `under` parameter.
728
+ Example: putting an `if` block inside a `repeat` block.
729
+ 2. **Value-level nesting (output blocks)**
730
+ These are blocks that produce a value (like a number, text, or expression). They can’t exist alone in the workspace - they must
731
+ be nested inside another block’s input. To create these, you can nest them directly in a single command, for example:
732
+ math_arithmetic(inputs(A: math_number(inputs(NUM: 1)), B: math_number(inputs(NUM: 1))))
733
+ Here, the two `math_number` blocks are nested inside the `math_arithmetic` block in one call.
 
 
 
734
 
735
  When creating blocks, you are never allowed to insert raw text or numbers directly into a block's inputs.
736
  Every value must be enclosed inside the correct block type that represents that value.
 
763
  which already exists. If you are trying to nest input blocks, you must create them all
764
  in one call.
765
 
766
+ But, for blocks that you want to stack that connect above or below to other blocks, you cannot
767
+ create both blocks in the same response. You must create one, wait, then create the other. You
768
+ need to wait and not do both in the same response because you need the ID of the first block.
769
+
770
  ### Variables
771
 
772
  You will be given the current variables that are in the workspace. Like the blocks, you will see:
 
790
  tools = [
791
  {
792
  "type": "function",
793
+ "name": "delete_block",
794
+ "description": "Delete a single block using its ID.",
795
+ "parameters": {
796
+ "type": "object",
797
+ "properties": {
798
+ "id": {
799
+ "type": "string",
800
+ "description": "The ID of the block you're trying to delete.",
 
 
801
  },
 
802
  },
803
+ "required": ["id"],
804
  }
805
  },
806
  {
807
  "type": "function",
808
+ "name": "create_block",
809
+ "description": "Creates a single block that allows recursive nested blocks.",
810
+ "parameters": {
811
+ "type": "object",
812
+ "properties": {
813
+ "command": {
814
+ "type": "string",
815
+ "description": "The create block command using the custom DSL format.",
816
+ },
817
+ "under": {
818
+ "type": "string",
819
+ "description": "The ID of the block that you want to place this under.",
 
 
820
  },
 
821
  },
822
+ "required": ["command"],
823
  }
824
  },
825
  {
826
  "type": "function",
827
+ "name": "create_variable",
828
+ "description": "Creates a variable.",
829
+ "parameters": {
830
+ "type": "object",
831
+ "properties": {
832
+ "name": {
833
+ "type": "string",
834
+ "description": "The name of the variable you want to create.",
 
 
835
  },
 
836
  },
837
+ "required": ["name"],
838
  }
839
  },
840
  {
841
  "type": "function",
842
+ "name": "run_mcp",
843
+ "description": "Runs the MCP with the given inputs. Create one parameter for each input that the user-created MCP allows.",
844
+ "parameters": {
845
+ "type": "object",
846
+ "properties": {},
847
+ "required": [],
848
+ "additionalProperties": True
 
 
849
  }
850
  },
851
  {
852
  "type": "function",
853
+ "name": "deploy_to_huggingface",
854
+ "description": "Deploy the generated MCP tool to a Hugging Face Space. Requires a Hugging Face API key to be set.",
855
+ "parameters": {
856
+ "type": "object",
857
+ "properties": {
858
+ "space_name": {
859
+ "type": "string",
860
+ "description": "The name of the Hugging Face Space to create (e.g., 'my-tool')",
 
 
861
  },
 
862
  },
863
+ "required": ["space_name"],
864
  }
865
  },
866
  ]
 
869
  # Check if API key is set and create/update client
870
  global client, stored_api_key
871
 
872
+ # Use stored key or environment key
873
  api_key = stored_api_key or os.environ.get("OPENAI_API_KEY")
874
 
875
  if api_key and (not client or (hasattr(client, 'api_key') and client.api_key != api_key)):
 
883
  yield "OpenAI API key not configured. Please set it in File > Settings in the Blockly interface."
884
  return
885
 
886
+ # Get chat context
887
  global latest_blockly_chat_code
888
  context = latest_blockly_chat_code
889
  global latest_blockly_vars
890
  vars = latest_blockly_vars
891
 
892
  # Convert history to OpenAI format
893
+ input_items = []
894
  for human, ai in history:
895
+ input_items.append({"role": "user", "content": human})
896
+ input_items.append({"role": "assistant", "content": ai})
897
+
898
+ # Debug
899
  print(f"[DEBUG] Context received: {context if context else 'No context available'}")
900
+
901
+ # Build instructions
902
+ instructions = SYSTEM_PROMPT
903
  if context:
904
+ instructions += f"\n\nCurrent Blockly workspace state:\n{context}"
905
  else:
906
+ instructions += "\n\nNote: No Blockly workspace context is currently available."
907
+
908
  if vars != "":
909
+ instructions += f"\n\nCurrent Blockly variables:\n{vars}"
910
  else:
911
+ instructions += "\n\nNote: No Blockly variables are currently available."
912
+
913
+ # Iteration control
914
  accumulated_response = ""
915
  max_iterations = 10
916
  current_iteration = 0
917
 
918
+ # Start with original user message
919
  current_prompt = message
920
+ temp_input_items = input_items.copy()
921
 
922
+ # MAIN LOOP
923
  while current_iteration < max_iterations:
924
  current_iteration += 1
925
 
926
  try:
927
+ # Create Responses API call
928
+ response = client.responses.create(
929
+ model="gpt-4o",
930
+ instructions=instructions,
931
+ input=temp_input_items + [{"role": "user", "content": current_prompt}],
 
 
 
932
  tools=tools,
933
+ tool_choice="auto"
934
  )
935
 
936
+ # print(response)
 
937
 
938
+ # Extract outputs
939
+ ai_response = ""
940
+ tool_calls = []
941
+
942
+ for item in response.output:
943
+
944
+ if item.type == "message":
945
+ # Extract assistant text
946
+ for content in item.content:
947
+ if content.type == "output_text":
948
+ ai_response = content.text
949
+
950
+ elif item.type == "function_call":
951
+ # Collect tool calls
952
+ tool_calls.append(item)
953
+
954
+ # PROCESSING TOOL CALLS
955
+ if tool_calls:
956
+
957
+ # Show assistant text FIRST if it exists
958
  if ai_response:
959
  if accumulated_response:
960
  accumulated_response += "\n\n"
961
  accumulated_response += ai_response
962
  yield accumulated_response
963
+
964
+ # Now process each tool call, one by one
965
+ for tool_call in tool_calls:
966
+ function_name = tool_call.name
967
+ function_args = json.loads(tool_call.arguments)
968
+ call_id = tool_call.call_id
969
+
970
+ temp_input_items.append({"role": "user", "content": current_prompt})
971
+ temp_input_items.append({"role": "assistant", "content": ai_response})
972
+
973
+ temp_input_items.append({
974
+ "type": "function_call",
975
+ "call_id": call_id,
976
+ "name": function_name,
977
+ "arguments": tool_call.arguments
978
+ })
979
 
980
+ # Execute the tool
981
  tool_result = None
982
  result_label = ""
983
 
 
986
  print(Fore.YELLOW + f"Agent deleted block with ID `{block_id}`." + Style.RESET_ALL)
987
  tool_result = delete_block(block_id)
988
  result_label = "Delete Operation"
989
+
990
  elif function_name == "create_block":
991
  command = function_args.get("command", "")
992
  under_block_id = function_args.get("under", None)
993
+ if under_block_id is None:
994
  print(Fore.YELLOW + f"Agent created block with command `{command}`." + Style.RESET_ALL)
995
  else:
996
+ print(Fore.YELLOW + f"Agent created block with command `{command}`, under block ID `{under_block_id}`." + Style.RESET_ALL)
997
  tool_result = create_block(command, under_block_id)
998
  result_label = "Create Operation"
999
+
1000
  elif function_name == "create_variable":
1001
  name = function_args.get("name", "")
1002
  print(Fore.YELLOW + f"Agent created variable with name `{name}`." + Style.RESET_ALL)
1003
  tool_result = create_variable(name)
1004
  result_label = "Create Var Operation"
1005
+
1006
  elif function_name == "run_mcp":
 
 
1007
  params = []
1008
  for key, value in function_args.items():
 
1009
  params.append(f"{key}=\"{value}\"")
1010
  mcp_call = f"create_mcp({', '.join(params)})"
1011
  print(Fore.YELLOW + f"Agent ran MCP with inputs: {mcp_call}." + Style.RESET_ALL)
 
1014
 
1015
  elif function_name == "deploy_to_huggingface":
1016
  space_name = function_args.get("space_name", "")
1017
+ print(Fore.YELLOW + f"Agent deploying to Hugging Face Space `{space_name}`." + Style.RESET_ALL)
1018
  tool_result = deploy_to_huggingface(space_name)
1019
  result_label = "Deployment Result"
1020
 
1021
+ # SHOW TOOL RESULT IMMEDIATELY
1022
+ if tool_result is not None:
1023
  print(Fore.YELLOW + f"[TOOL RESULT] {tool_result}" + Style.RESET_ALL)
1024
 
 
1025
  if accumulated_response:
1026
  accumulated_response += "\n\n"
1027
  accumulated_response += f"**{result_label}:** {tool_result}"
1028
  yield accumulated_response
1029
+
1030
+ # Append the tool result into the conversation for the model
1031
+ temp_input_items.append({
1032
+ "type": "function_call_output",
1033
+ "call_id": tool_call.call_id,
1034
+ "output": str(tool_result)
1035
+ })
 
1036
 
1037
+ # Tell model to respond to tool result
1038
+ current_prompt = "The tool has been executed with the result shown above. Please respond appropriately."
1039
 
1040
+ continue # Continue the main loop
1041
+
1042
  else:
1043
+ if ai_response:
1044
+ if accumulated_response:
1045
+ accumulated_response += "\n\n"
1046
+ accumulated_response += ai_response
1047
+
1048
  yield accumulated_response
1049
  break
1050
+
1051
  except Exception as e:
1052
  if accumulated_response:
1053
  yield f"{accumulated_response}\n\nError in iteration {current_iteration}: {str(e)}"
 
1055
  yield f"Error: {str(e)}"
1056
  return
1057
 
1058
+ # Max iterations reached
1059
  if current_iteration >= max_iterations:
1060
  accumulated_response += f"\n\n*(Reached maximum of {max_iterations} consecutive responses)*"
1061
  yield accumulated_response
1062
+
1063
+
1064
+ # Attach to Gradio ChatInterface
1065
  demo = gr.ChatInterface(
1066
  fn=chat_with_context,
1067
  title="AI Assistant",