From 54775faaf4c99297b4a7808f10187577fcbe5bc1 Mon Sep 17 00:00:00 2001 From: YueGuobin Date: Thu, 5 Mar 2026 21:44:36 +0800 Subject: [PATCH] feat(copilot): rename copilot modes for clarity and add mode-specific tools - Rename "teaching" mode to "teaching_assistant" for better clarity - Rename "lab_assistant" mode to "lab_automation_assistant" to reflect expanded capabilities - Implement mode-specific tool sets: teaching_assistant gets read-only diagnostic tools only, while lab_automation_assistant gets full diagnostic and configuration tools - Update API documentation examples to reflect new mode names - Maintain backward compatibility with default tool set initialization --- docs/gns3-copilot/llm-model-configs-api.md | 8 +-- .../agent/gns3_copilot/agent/gns3_copilot.py | 55 +++++++++++++++---- .../agent/gns3_copilot/prompts/__init__.py | 2 +- .../gns3_copilot/prompts/prompt_loader.py | 18 +++--- .../schemas/controller/llm_model_configs.py | 6 +- 5 files changed, 61 insertions(+), 28 deletions(-) diff --git a/docs/gns3-copilot/llm-model-configs-api.md b/docs/gns3-copilot/llm-model-configs-api.md index 3f4bb7db5..8538164bf 100644 --- a/docs/gns3-copilot/llm-model-configs-api.md +++ b/docs/gns3-copilot/llm-model-configs-api.md @@ -124,7 +124,7 @@ The `model_type` field accepts the following values: | `api_key` | string | API key (auto-encrypted) | | `max_tokens` | integer | Max tokens for generation | | `context_strategy` | string | Context trimming strategy: "conservative" (60%), "balanced" (75%), "aggressive" (85%). Default: "balanced" | -| `copilot_mode` | string | GNS3-Copilot mode: "teaching" (diagnostics only, default) or "lab_assistant" (full configuration access) | +| `copilot_mode` | string | GNS3-Copilot mode: "teaching_assistant" (diagnostics only, default) or "lab_automation_assistant" (full configuration access) | | `is_default` | boolean | Set as default (default: false) | **Important Notes:** @@ -235,7 +235,7 @@ curl -X POST http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \ "context_limit": 128, "context_strategy": "balanced", "api_key": "sk-xxx", - "copilot_mode": "teaching", + "copilot_mode": "teaching_assistant", "is_default": true }' ``` @@ -285,7 +285,7 @@ curl -X POST http://localhost:3080/v3/access/groups/{group_id}/llm-model-configs "context_limit": 200, "context_strategy": "balanced", "api_key": "sk-ant-xxx", - "copilot_mode": "lab_assistant", + "copilot_mode": "lab_automation_assistant", "is_default": true }' ``` @@ -313,7 +313,7 @@ curl -X GET http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \ "context_limit": 128, "context_strategy": "balanced", "api_key": "sk-xxx", - "copilot_mode": "lab_assistant" + "copilot_mode": "lab_automation_assistant" }, "user_id": "uuid-user", "group_id": null, diff --git a/gns3server/agent/gns3_copilot/agent/gns3_copilot.py b/gns3server/agent/gns3_copilot/agent/gns3_copilot.py index b0016d61b..50c0deb1c 100644 --- a/gns3server/agent/gns3_copilot/agent/gns3_copilot.py +++ b/gns3server/agent/gns3_copilot/agent/gns3_copilot.py @@ -33,11 +33,15 @@ an AI-powered assistant for GNS3 network automation and management. The agent provides: - LangGraph-based state management and workflow -- Tool orchestration for GNS3 operations +- Mode-aware tool orchestration for GNS3 operations - Context-aware conversation handling - Automatic conversation title generation - Integration with GNS3 topology management +Copilot Modes: +- "teaching_assistant" (default): Diagnostic tools only, no configuration changes +- "lab_automation_assistant": Full diagnostic and configuration tools + """ # Standard library imports @@ -84,20 +88,36 @@ logger = logging.getLogger(__name__) # Note: LLM model configuration is now managed by the new llm_model_configs system. # The model_factory module handles model creation with configuration from the database. -# Define the available tools for the agent -tools = [ +# Define tools for different copilot modes +# Teaching assistant mode: READ-ONLY diagnostic tools only +TEACHING_ASSISTANT_MODE_TOOLS = [ GNS3TemplateTool(), # Get GNS3 node templates GNS3CreateNodeTool(), # Create new nodes in GNS3 GNS3LinkTool(), # Create links between nodes GNS3StartNodeTool(), # Start GNS3 nodes GNS3UpdateNodeNameTool(), # Update node name - ExecuteMultipleDeviceCommands(), # Execute show/display/debug commands on multiple devices (READ-ONLY) - ExecuteMultipleDeviceConfigCommands(), # Execute configuration commands on multiple devices + ExecuteMultipleDeviceCommands(), # Execute show/display/debug commands (READ-ONLY) +] + +# Lab automation assistant mode: Full diagnostic AND configuration tools +LAB_AUTOMATION_ASSISTANT_MODE_TOOLS = [ + GNS3TemplateTool(), # Get GNS3 node templates + GNS3CreateNodeTool(), # Create new nodes in GNS3 + GNS3LinkTool(), # Create links between nodes + GNS3StartNodeTool(), # Start GNS3 nodes + GNS3UpdateNodeNameTool(), # Update node name + ExecuteMultipleDeviceCommands(), # Execute show/display/debug commands (READ-ONLY) + ExecuteMultipleDeviceConfigCommands(), # Execute configuration commands VPCSMultiCommands(), # Execute VPCS commands on multiple devices ] -# Augment the LLM with tools -tools_by_name = {tool.name: tool for tool in tools} -# Model with tools will be created dynamically by the factory when needed + +# Default tools (legacy support - will be overridden by mode-specific tools) +tools = LAB_AUTOMATION_ASSISTANT_MODE_TOOLS + +# Create combined tool lookup for tool_node (supports both modes) +# tool_node will receive tool calls based on mode-specific tools bound to the model +ALL_TOOLS = LAB_AUTOMATION_ASSISTANT_MODE_TOOLS +tools_by_name = {tool.name: tool for tool in ALL_TOOLS} # Log application startup logger.info("GNS3-Copilot application starting up") @@ -203,6 +223,15 @@ def llm_call(state: dict, config: RunnableConfig | None = None): # Store topology_info in state for pre_model_hook to access state["topology_info"] = topology_info + # Select tools based on copilot_mode + copilot_mode = llm_config.get("copilot_mode", "teaching_assistant").lower() + if copilot_mode == "lab_automation_assistant": + mode_tools = LAB_AUTOMATION_ASSISTANT_MODE_TOOLS + logger.info("Using LAB_AUTOMATION_ASSISTANT mode tools (includes configuration tools)") + else: # teaching_assistant mode (default) + mode_tools = TEACHING_ASSISTANT_MODE_TOOLS + logger.info("Using TEACHING_ASSISTANT mode tools (diagnostic tools only)") + # Create pre_model_hook for automatic topology injection and trimming # Load system prompt based on copilot_mode configuration system_prompt = load_system_prompt(llm_config) @@ -210,14 +239,18 @@ def llm_call(state: dict, config: RunnableConfig | None = None): system_prompt=system_prompt, get_topology_func=lambda s: s.get("topology_info"), get_llm_config_func=get_current_llm_config, - get_tools_func=lambda: tools, # Pass tools for token estimation + get_tools_func=lambda: mode_tools, # Pass mode-specific tools for token estimation ) # Create fresh model with tools for each LLM call logger.debug( - "Creating model with tools: provider=%s, model=%s", llm_config.get("provider"), llm_config.get("model") + "Creating model with tools: provider=%s, model=%s, mode=%s, tools=%d", + llm_config.get("provider"), + llm_config.get("model"), + copilot_mode, + len(mode_tools), ) - model_with_tools = create_base_model_with_tools(tools, llm_config=llm_config) + model_with_tools = create_base_model_with_tools(mode_tools, llm_config=llm_config) # Call pre_hook directly to prepare messages (topology injection + trimming) # Note: LangGraph's pre_model_hook only works with prebuilt agents, not custom StateGraph diff --git a/gns3server/agent/gns3_copilot/prompts/__init__.py b/gns3server/agent/gns3_copilot/prompts/__init__.py index d11aee333..4c3058c16 100644 --- a/gns3server/agent/gns3_copilot/prompts/__init__.py +++ b/gns3server/agent/gns3_copilot/prompts/__init__.py @@ -31,7 +31,7 @@ the GNS3-Copilot AI agent. Available prompts: - base_prompt: Teaching assistant mode (diagnostics only, no configuration) -- lab_assistant_prompt: Full lab assistant mode (diagnostics + configuration) +- lab_assistant_prompt: Lab automation assistant mode (diagnostics + configuration) """ diff --git a/gns3server/agent/gns3_copilot/prompts/prompt_loader.py b/gns3server/agent/gns3_copilot/prompts/prompt_loader.py index 676fffc16..1f062d0c2 100644 --- a/gns3server/agent/gns3_copilot/prompts/prompt_loader.py +++ b/gns3server/agent/gns3_copilot/prompts/prompt_loader.py @@ -30,8 +30,8 @@ This module provides utilities for loading system prompts. Supports multiple prompt variants based on LLM model configuration. Available Modes (controlled by config.copilot_mode in llm_model_configs): -- "teaching" (default): Teaching assistant mode - diagnostics only, no configuration -- "lab_assistant": Full lab assistant mode - diagnostics and configuration enabled +- "teaching_assistant" (default): Teaching assistant mode - diagnostics only, no configuration +- "lab_automation_assistant": Full lab automation assistant mode - diagnostics and configuration enabled """ @@ -48,8 +48,8 @@ def load_system_prompt(llm_config: dict | None = None) -> str: Load the system prompt for GNS3-Copilot. The prompt mode is controlled by the `copilot_mode` field in the LLM model config: - - "teaching" (default): Teaching assistant mode - diagnostics only, no configuration - - "lab_assistant": Full lab assistant mode - diagnostics and configuration enabled + - "teaching_assistant" (default): Teaching assistant mode - diagnostics only, no configuration + - "lab_automation_assistant": Full lab automation assistant mode - diagnostics and configuration enabled Args: llm_config: LLM model configuration dictionary (flattened structure from get_user_llm_config_full) @@ -58,16 +58,16 @@ def load_system_prompt(llm_config: dict | None = None) -> str: str: The system prompt string. """ if not llm_config: - logger.info("No LLM config provided, using default TEACHING assistant prompt mode") + logger.info("No LLM config provided, using default TEACHING_ASSISTANT prompt mode") return SYSTEM_PROMPT # llm_config is a flattened dict with copilot_mode at the top level # Example: {"provider": "...", "model": "...", "copilot_mode": "...", ...} - mode = llm_config.get("copilot_mode", "teaching").lower() + mode = llm_config.get("copilot_mode", "teaching_assistant").lower() - if mode == "lab_assistant": - logger.info("Using LAB_ASSISTANT prompt mode (diagnostics + configuration)") + if mode == "lab_automation_assistant": + logger.info("Using LAB_AUTOMATION_ASSISTANT prompt mode (diagnostics + configuration)") return LAB_ASSISTANT_PROMPT else: - logger.info("Using TEACHING assistant prompt mode (diagnostics only)") + logger.info("Using TEACHING_ASSISTANT prompt mode (diagnostics only)") return SYSTEM_PROMPT diff --git a/gns3server/schemas/controller/llm_model_configs.py b/gns3server/schemas/controller/llm_model_configs.py index 7fe992928..7b7617edc 100644 --- a/gns3server/schemas/controller/llm_model_configs.py +++ b/gns3server/schemas/controller/llm_model_configs.py @@ -50,7 +50,7 @@ class LLMModelConfigData(BaseModel): ) copilot_mode: Optional[str] = Field( None, - description="GNS3-Copilot mode: 'teaching' (diagnostics only) or 'lab_assistant' (full configuration access)" + description="GNS3-Copilot mode: 'teaching_assistant' (diagnostics only) or 'lab_automation_assistant' (full configuration access)" ) # Allow extra fields for extensibility @@ -78,7 +78,7 @@ class LLMModelConfigCreate(BaseModel): ) copilot_mode: Optional[str] = Field( None, - description="GNS3-Copilot mode: 'teaching' (diagnostics only) or 'lab_assistant' (full configuration access)" + description="GNS3-Copilot mode: 'teaching_assistant' (diagnostics only) or 'lab_automation_assistant' (full configuration access)" ) # Allow extra config fields @@ -107,7 +107,7 @@ class LLMModelConfigUpdate(BaseModel): ) copilot_mode: Optional[str] = Field( None, - description="GNS3-Copilot mode: 'teaching' (diagnostics only) or 'lab_assistant' (full configuration access)" + description="GNS3-Copilot mode: 'teaching_assistant' (diagnostics only) or 'lab_automation_assistant' (full configuration access)" ) # Allow extra config fields