owenkaplinsky commited on
Commit
e53c90b
·
1 Parent(s): 959d85a

Switch to openai tool; validate key

Browse files
Files changed (2) hide show
  1. project/chat.py +131 -119
  2. project/src/index.js +8 -2
project/chat.py CHANGED
@@ -422,64 +422,44 @@ async def deletion_result(request: Request):
422
 
423
  def create_gradio_interface():
424
  # Hardcoded system prompt
425
- SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
426
  MCP lets AI systems define tools with specific inputs and outputs that any LLM can call.
427
 
428
- You’ll receive the workspace state in this format:
429
- `blockId | block_name(inputs(input_name: value))`
430
 
431
- **Special cases:**
432
- - `create_mcp` and `func_def` use
433
- `blockId | block_name(inputs(input_name: type), outputs(output_name: value))`
434
- - Indentation or nesting shows logic hierarchy (like loops or conditionals).
435
  - The `blockId` before the pipe `|` is each block’s unique identifier.
436
 
437
  ---
438
 
439
  ### Your job
440
- - Help users understand or fix their MCP logic in natural, human language.
441
- - Never mention the internal block syntax or say “multi-context-protocol.” Just call it **MCP**.
442
  - Focus on what the code *does* and what the user is trying to achieve, not on the raw block format.
443
 
444
  ---
445
 
446
  ### Using Tools
447
- Before using any tool, **explicitly plan** what you will do.
448
- You can only use **one tool per message** - NEVER EVER combine multiple tool calls in one message.
449
- If you need two actions, use two messages.
450
- When you invoke a tool, it must be the **last thing in your message**.
451
-
452
- To call a tool, use this exact format (no newline after the opening backticks):
453
-
454
- ```name
455
- (arguments_here)
456
  ```
457
 
458
  ---
459
 
460
  ### Running MCP
461
- You can execute the MCP directly:
462
-
463
- ```run
464
- create_mcp(input_name=value)
465
- ```
466
-
467
- Use plain Python-style arguments (no `inputs()` wrapper).
468
- That’s how you actually run the MCP.
469
 
470
  ---
471
 
472
  ### Deleting Blocks
473
- Each block starts with its ID, like `blockId | block_name(...)`.
474
  To delete one, end your message with:
475
-
476
- ```delete
477
- blockId
478
- ```
479
-
480
  You can delete any block except the main `create_mcp` block.
481
-
482
- Remember, you give the blockId. Never put the code for it!
483
  You can see the ID to the left of each block it will be a jarble of characters
484
  looking something like:
485
 
@@ -491,19 +471,12 @@ the correct block.
491
  ---
492
 
493
  ### Creating Blocks
494
- You can create new blocks in the workspace.
495
- To create a block, specify its type and parameters (if any).
496
- End your message (and say nothing after) with:
497
-
498
- ```create
499
- block_name(inputs(value_name: value))
500
- ```
501
 
502
  If you want to create a block inside of a block, do it like this:
503
 
504
- ```create
505
  block_name(inputs(value_name: block_name2(inputs(value_name2: value))))
506
- ```
507
 
508
  Where you specify inputs() per block, even if it's inside of another block.
509
 
@@ -511,19 +484,59 @@ List of blocks:
511
 
512
  {blocks_context}
513
 
514
- ---
515
-
516
- Remember, this is not the standard tool format. You must follow the one outlined above.
517
-
518
- Never say that you are going to run a tool and don't run it. You need to put tool calls in the same
519
- message or your messages will end (see below).
520
-
521
- Additionally, if you ever send a message without a tool call, your response will end. So, if you want to
522
- call more tools after something you have to keep calling them. Any pause in tool callings ends the loop.
523
-
524
- For deleting blocks, don't forget that you use the **blockId** and **not** the code for it.
525
  """
526
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  def chat_with_context(message, history):
528
  # Check if API key is set and create/update client
529
  global client, stored_api_key
@@ -562,9 +575,9 @@ For deleting blocks, don't forget that you use the **blockId** and **not** the c
562
  else:
563
  full_system_prompt += "\n\nNote: No Blockly workspace context is currently available."
564
 
565
- # Allow up to 5 consecutive messages from the agent
566
  accumulated_response = ""
567
- max_iterations = 5
568
  current_iteration = 0
569
 
570
  # Start with the user's original message
@@ -575,86 +588,85 @@ For deleting blocks, don't forget that you use the **blockId** and **not** the c
575
  current_iteration += 1
576
 
577
  try:
 
578
  response = client.chat.completions.create(
579
  model="gpt-4o-2024-08-06",
580
  messages=[
581
  {"role": "system", "content": full_system_prompt},
582
  *temp_history,
583
  {"role": "user", "content": current_prompt}
584
- ]
 
 
585
  )
586
 
587
- ai_response = response.choices[0].message.content
 
588
 
589
- # Define action patterns and their handlers
590
- action_patterns = {
591
- 'run': {
592
- 'pattern': r'```(?:\n)?run\n(.+?)\n```',
593
- 'label': 'MCP',
594
- 'result_label': 'MCP Execution Result',
595
- 'handler': lambda content: execute_mcp(content),
596
- 'next_prompt': "Please respond to the MCP execution result above and provide any relevant information to the user. If you need to run another MCP, delete, or create blocks, you can do so."
597
- },
598
- 'delete': {
599
- 'pattern': r'```(?:\n)?delete\n(.+?)\n```',
600
- 'label': 'DELETE',
601
- 'result_label': 'Delete Operation',
602
- 'handler': lambda content: delete_block(content.strip()),
603
- 'next_prompt': "Please respond to the delete operation result above. If you need to run an MCP, delete more code, or create blocks, you can do so."
604
- },
605
- 'create': {
606
- 'pattern': r'```(?:\n)?create\n(.+?)\n```',
607
- 'label': 'CREATE',
608
- 'result_label': 'Create Operation',
609
- 'handler': lambda content: create_block(content.strip()),
610
- 'next_prompt': "Please respond to the create operation result above. If you need to run an MCP, delete code, or create more blocks, you can do so."
611
- }
612
- }
613
-
614
- # Check for action blocks
615
- action_found = False
616
- for action_type, config in action_patterns.items():
617
- match = re.search(config['pattern'], ai_response, re.DOTALL)
618
- if match:
619
- action_found = True
620
 
621
- # Extract content and filter the action block from displayed message
622
- action_content = match.group(1)
623
- displayed_response = ai_response[:match.start()].rstrip()
624
 
625
- print(f"[{config['label']} DETECTED] Processing: {action_content}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
626
 
627
- # Yield the displayed response first if it exists
628
- if displayed_response:
 
 
629
  if accumulated_response:
630
  accumulated_response += "\n\n"
631
- accumulated_response += displayed_response
632
  yield accumulated_response
633
-
634
- # Execute the action
635
- action_result = config['handler'](action_content)
636
-
637
- print(f"[{config['label']} RESULT] {action_result}")
638
-
639
- # Yield the action result
640
- if accumulated_response:
641
- accumulated_response += "\n\n"
642
- accumulated_response += f"**{config['result_label']}:** {action_result}"
643
- yield accumulated_response
644
-
645
- # Update history for next iteration
646
- temp_history.append({"role": "user", "content": current_prompt})
647
- temp_history.append({"role": "assistant", "content": ai_response})
648
- temp_history.append({"role": "system", "content": f"{config['result_label']}: {action_result}"})
649
-
650
- # Set up next prompt
651
- current_prompt = config['next_prompt']
652
- break
653
-
654
- if action_found:
655
  continue
 
656
  else:
657
- # No action blocks found, this is the final response
658
  if accumulated_response:
659
  accumulated_response += "\n\n"
660
  accumulated_response += ai_response
 
422
 
423
  def create_gradio_interface():
424
  # Hardcoded system prompt
425
+ SYSTEM_PROMPT = f"""You are an AI assistant that helps users build **MCP servers** using Blockly blocks.
426
  MCP lets AI systems define tools with specific inputs and outputs that any LLM can call.
427
 
428
+ You’ll receive the workspace state in this format:
429
+ `blockId | block_name(inputs(input_name: value))`
430
 
431
+ **Special cases:**
432
+ - `create_mcp` and `func_def` use
433
+ `blockId | block_name(inputs(input_name: type), outputs(output_name: value))`
434
+ - Indentation or nesting shows logic hierarchy (like loops or conditionals).
435
  - The `blockId` before the pipe `|` is each block’s unique identifier.
436
 
437
  ---
438
 
439
  ### Your job
440
+ - Help users understand or fix their MCP logic in natural, human language.
441
+ - Never mention the internal block syntax or say “multi-context-protocol.” Just call it **MCP**.
442
  - Focus on what the code *does* and what the user is trying to achieve, not on the raw block format.
443
 
444
  ---
445
 
446
  ### Using Tools
447
+ Before using any tool, **explicitly plan** what you will do.
448
+ You can only use **one tool per message** - NEVER EVER combine multiple tool calls in one message.
449
+ If you need two actions, use two messages.
 
 
 
 
 
 
450
  ```
451
 
452
  ---
453
 
454
  ### Running MCP
455
+ You can execute the MCP directly and get the result back.
 
 
 
 
 
 
 
456
 
457
  ---
458
 
459
  ### Deleting Blocks
460
+ Each block starts with its ID, like `blockId | block_name(...)`.
461
  To delete one, end your message with:
 
 
 
 
 
462
  You can delete any block except the main `create_mcp` block.
 
 
463
  You can see the ID to the left of each block it will be a jarble of characters
464
  looking something like:
465
 
 
471
  ---
472
 
473
  ### Creating Blocks
474
+ You can create new blocks in the workspace.
475
+ To create a block, specify its type and parameters (if any).
 
 
 
 
 
476
 
477
  If you want to create a block inside of a block, do it like this:
478
 
 
479
  block_name(inputs(value_name: block_name2(inputs(value_name2: value))))
 
480
 
481
  Where you specify inputs() per block, even if it's inside of another block.
482
 
 
484
 
485
  {blocks_context}
486
 
487
+ YOU CANNOT CREATE A MCP BLOCK OR EDIT ITS INPUTS. YOU MUST TELL THE USER TO DO SO.
 
 
 
 
 
 
 
 
 
 
488
  """
489
 
490
+ tools = [
491
+ {
492
+ "type": "function",
493
+ "function": {
494
+ "name": "delete_block",
495
+ "description": "Delete a single block using its ID.",
496
+ "parameters": {
497
+ "type": "object",
498
+ "properties": {
499
+ "id": {
500
+ "type": "string",
501
+ "description": "The ID of the block you're trying to delete.",
502
+ },
503
+ },
504
+ "required": ["id"],
505
+ },
506
+ }
507
+ },
508
+ {
509
+ "type": "function",
510
+ "function": {
511
+ "name": "create_block",
512
+ "description": "Creates a single block that allows recursive nested blocks.",
513
+ "parameters": {
514
+ "type": "object",
515
+ "properties": {
516
+ "command": {
517
+ "type": "string",
518
+ "description": "The create block command using the custom DSL format.",
519
+ },
520
+ },
521
+ "required": ["command"],
522
+ },
523
+ }
524
+ },
525
+ {
526
+ "type": "function",
527
+ "function": {
528
+ "name": "run_mcp",
529
+ "description": "Runs the MCP with the given inputs. Create one parameter for each input that the user-created MCP allows.",
530
+ "parameters": {
531
+ "type": "object",
532
+ "properties": {},
533
+ "required": [],
534
+ "additionalProperties": True
535
+ },
536
+ }
537
+ },
538
+ ]
539
+
540
  def chat_with_context(message, history):
541
  # Check if API key is set and create/update client
542
  global client, stored_api_key
 
575
  else:
576
  full_system_prompt += "\n\nNote: No Blockly workspace context is currently available."
577
 
578
+ # Allow up to 10 consecutive messages from the agent
579
  accumulated_response = ""
580
+ max_iterations = 10
581
  current_iteration = 0
582
 
583
  # Start with the user's original message
 
588
  current_iteration += 1
589
 
590
  try:
591
+ # Create the completion request with tools
592
  response = client.chat.completions.create(
593
  model="gpt-4o-2024-08-06",
594
  messages=[
595
  {"role": "system", "content": full_system_prompt},
596
  *temp_history,
597
  {"role": "user", "content": current_prompt}
598
+ ],
599
+ tools=tools,
600
+ tool_choice="auto" # Let the model decide whether to use tools
601
  )
602
 
603
+ response_message = response.choices[0].message
604
+ ai_response = response_message.content or ""
605
 
606
+ # Check if the model wants to use tools
607
+ if response_message.tool_calls:
608
+ # Display the AI's message before executing tools (if any)
609
+ if ai_response:
610
+ if accumulated_response:
611
+ accumulated_response += "\n\n"
612
+ accumulated_response += ai_response
613
+ yield accumulated_response
614
+
615
+ # Process each tool call
616
+ for tool_call in response_message.tool_calls:
617
+ function_name = tool_call.function.name
618
+ function_args = json.loads(tool_call.function.arguments)
619
+
620
+ print(f"[TOOL CALL] {function_name} with args: {function_args}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
 
622
+ # Execute the appropriate function
623
+ tool_result = None
624
+ result_label = ""
625
 
626
+ if function_name == "delete_block":
627
+ block_id = function_args.get("id", "")
628
+ tool_result = delete_block(block_id)
629
+ result_label = "Delete Operation"
630
+
631
+ elif function_name == "create_block":
632
+ command = function_args.get("command", "")
633
+ tool_result = create_block(command)
634
+ result_label = "Create Operation"
635
+
636
+ elif function_name == "run_mcp":
637
+ # Build the MCP call string from the arguments
638
+ # run_mcp receives dynamic arguments based on the MCP's inputs
639
+ params = []
640
+ for key, value in function_args.items():
641
+ # Format as key=value for execute_mcp
642
+ params.append(f"{key}=\"{value}\"")
643
+ mcp_call = f"create_mcp({', '.join(params)})"
644
+ print(f"[MCP CALL] Executing: {mcp_call}")
645
+ tool_result = execute_mcp(mcp_call)
646
+ result_label = "MCP Execution Result"
647
 
648
+ if tool_result:
649
+ print(f"[TOOL RESULT] {tool_result}")
650
+
651
+ # Yield the tool result
652
  if accumulated_response:
653
  accumulated_response += "\n\n"
654
+ accumulated_response += f"**{result_label}:** {tool_result}"
655
  yield accumulated_response
656
+
657
+ # Update history with the tool call and result
658
+ temp_history.append({"role": "user", "content": current_prompt})
659
+ temp_history.append({"role": "assistant", "content": ai_response, "tool_calls": response_message.tool_calls})
660
+ temp_history.append({"role": "tool", "tool_call_id": tool_call.id, "content": tool_result})
661
+
662
+ # Set up next prompt to have the model respond to the tool result
663
+ current_prompt = f"The tool has been executed with the result shown above. Please respond appropriately to the user based on this result."
664
+
665
+ # Continue to next iteration if tools were used
 
 
 
 
 
 
 
 
 
 
 
 
666
  continue
667
+
668
  else:
669
+ # No tool calls, this is a regular response
670
  if accumulated_response:
671
  accumulated_response += "\n\n"
672
  accumulated_response += ai_response
project/src/index.js CHANGED
@@ -142,8 +142,14 @@ settingsButton.addEventListener("click", () => {
142
  });
143
 
144
  saveApiKeyButton.addEventListener("click", () => {
145
- const apiKey = apiKeyInput.value;
146
-
 
 
 
 
 
 
147
  // Save API key to both backend servers (test.py and chat.py)
148
  Promise.all([
149
  fetch("http://127.0.0.1:7860/set_api_key", {
 
142
  });
143
 
144
  saveApiKeyButton.addEventListener("click", () => {
145
+ const apiKey = apiKeyInput.value.trim();
146
+
147
+ // Validate OpenAI key format
148
+ if (!apiKey.startsWith("sk-") || apiKey.length < 40) {
149
+ alert("Invalid API key format. Please enter a valid OpenAI API key.");
150
+ return;
151
+ }
152
+
153
  // Save API key to both backend servers (test.py and chat.py)
154
  Promise.all([
155
  fetch("http://127.0.0.1:7860/set_api_key", {