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
This commit is contained in:
YueGuobin 2026-03-05 21:44:36 +08:00
parent 28f2fe03e6
commit 54775faaf4
5 changed files with 61 additions and 28 deletions

View File

@ -124,7 +124,7 @@ The `model_type` field accepts the following values:
| `api_key` | string | API key (auto-encrypted) | | `api_key` | string | API key (auto-encrypted) |
| `max_tokens` | integer | Max tokens for generation | | `max_tokens` | integer | Max tokens for generation |
| `context_strategy` | string | Context trimming strategy: "conservative" (60%), "balanced" (75%), "aggressive" (85%). Default: "balanced" | | `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) | | `is_default` | boolean | Set as default (default: false) |
**Important Notes:** **Important Notes:**
@ -235,7 +235,7 @@ curl -X POST http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \
"context_limit": 128, "context_limit": 128,
"context_strategy": "balanced", "context_strategy": "balanced",
"api_key": "sk-xxx", "api_key": "sk-xxx",
"copilot_mode": "teaching", "copilot_mode": "teaching_assistant",
"is_default": true "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_limit": 200,
"context_strategy": "balanced", "context_strategy": "balanced",
"api_key": "sk-ant-xxx", "api_key": "sk-ant-xxx",
"copilot_mode": "lab_assistant", "copilot_mode": "lab_automation_assistant",
"is_default": true "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_limit": 128,
"context_strategy": "balanced", "context_strategy": "balanced",
"api_key": "sk-xxx", "api_key": "sk-xxx",
"copilot_mode": "lab_assistant" "copilot_mode": "lab_automation_assistant"
}, },
"user_id": "uuid-user", "user_id": "uuid-user",
"group_id": null, "group_id": null,

View File

@ -33,11 +33,15 @@ an AI-powered assistant for GNS3 network automation and management.
The agent provides: The agent provides:
- LangGraph-based state management and workflow - LangGraph-based state management and workflow
- Tool orchestration for GNS3 operations - Mode-aware tool orchestration for GNS3 operations
- Context-aware conversation handling - Context-aware conversation handling
- Automatic conversation title generation - Automatic conversation title generation
- Integration with GNS3 topology management - 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 # 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. # 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. # The model_factory module handles model creation with configuration from the database.
# Define the available tools for the agent # Define tools for different copilot modes
tools = [ # Teaching assistant mode: READ-ONLY diagnostic tools only
TEACHING_ASSISTANT_MODE_TOOLS = [
GNS3TemplateTool(), # Get GNS3 node templates GNS3TemplateTool(), # Get GNS3 node templates
GNS3CreateNodeTool(), # Create new nodes in GNS3 GNS3CreateNodeTool(), # Create new nodes in GNS3
GNS3LinkTool(), # Create links between nodes GNS3LinkTool(), # Create links between nodes
GNS3StartNodeTool(), # Start GNS3 nodes GNS3StartNodeTool(), # Start GNS3 nodes
GNS3UpdateNodeNameTool(), # Update node name GNS3UpdateNodeNameTool(), # Update node name
ExecuteMultipleDeviceCommands(), # Execute show/display/debug commands on multiple devices (READ-ONLY) ExecuteMultipleDeviceCommands(), # Execute show/display/debug commands (READ-ONLY)
ExecuteMultipleDeviceConfigCommands(), # Execute configuration commands on multiple devices ]
# 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 VPCSMultiCommands(), # Execute VPCS commands on multiple devices
] ]
# Augment the LLM with tools
tools_by_name = {tool.name: tool for tool in tools} # Default tools (legacy support - will be overridden by mode-specific tools)
# Model with tools will be created dynamically by the factory when needed 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 # Log application startup
logger.info("GNS3-Copilot application starting up") 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 # Store topology_info in state for pre_model_hook to access
state["topology_info"] = topology_info 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 # Create pre_model_hook for automatic topology injection and trimming
# Load system prompt based on copilot_mode configuration # Load system prompt based on copilot_mode configuration
system_prompt = load_system_prompt(llm_config) system_prompt = load_system_prompt(llm_config)
@ -210,14 +239,18 @@ def llm_call(state: dict, config: RunnableConfig | None = None):
system_prompt=system_prompt, system_prompt=system_prompt,
get_topology_func=lambda s: s.get("topology_info"), get_topology_func=lambda s: s.get("topology_info"),
get_llm_config_func=get_current_llm_config, 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 # Create fresh model with tools for each LLM call
logger.debug( 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) # Call pre_hook directly to prepare messages (topology injection + trimming)
# Note: LangGraph's pre_model_hook only works with prebuilt agents, not custom StateGraph # Note: LangGraph's pre_model_hook only works with prebuilt agents, not custom StateGraph

View File

@ -31,7 +31,7 @@ the GNS3-Copilot AI agent.
Available prompts: Available prompts:
- base_prompt: Teaching assistant mode (diagnostics only, no configuration) - 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)
""" """

View File

@ -30,8 +30,8 @@ This module provides utilities for loading system prompts.
Supports multiple prompt variants based on LLM model configuration. Supports multiple prompt variants based on LLM model configuration.
Available Modes (controlled by config.copilot_mode in llm_model_configs): Available Modes (controlled by config.copilot_mode in llm_model_configs):
- "teaching" (default): Teaching assistant mode - diagnostics only, no configuration - "teaching_assistant" (default): Teaching assistant mode - diagnostics only, no configuration
- "lab_assistant": Full lab assistant mode - diagnostics and configuration enabled - "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. Load the system prompt for GNS3-Copilot.
The prompt mode is controlled by the `copilot_mode` field in the LLM model config: The prompt mode is controlled by the `copilot_mode` field in the LLM model config:
- "teaching" (default): Teaching assistant mode - diagnostics only, no configuration - "teaching_assistant" (default): Teaching assistant mode - diagnostics only, no configuration
- "lab_assistant": Full lab assistant mode - diagnostics and configuration enabled - "lab_automation_assistant": Full lab automation assistant mode - diagnostics and configuration enabled
Args: Args:
llm_config: LLM model configuration dictionary (flattened structure from get_user_llm_config_full) 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. str: The system prompt string.
""" """
if not llm_config: 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 return SYSTEM_PROMPT
# llm_config is a flattened dict with copilot_mode at the top level # llm_config is a flattened dict with copilot_mode at the top level
# Example: {"provider": "...", "model": "...", "copilot_mode": "...", ...} # 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": if mode == "lab_automation_assistant":
logger.info("Using LAB_ASSISTANT prompt mode (diagnostics + configuration)") logger.info("Using LAB_AUTOMATION_ASSISTANT prompt mode (diagnostics + configuration)")
return LAB_ASSISTANT_PROMPT return LAB_ASSISTANT_PROMPT
else: else:
logger.info("Using TEACHING assistant prompt mode (diagnostics only)") logger.info("Using TEACHING_ASSISTANT prompt mode (diagnostics only)")
return SYSTEM_PROMPT return SYSTEM_PROMPT

View File

@ -50,7 +50,7 @@ class LLMModelConfigData(BaseModel):
) )
copilot_mode: Optional[str] = Field( copilot_mode: Optional[str] = Field(
None, 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 # Allow extra fields for extensibility
@ -78,7 +78,7 @@ class LLMModelConfigCreate(BaseModel):
) )
copilot_mode: Optional[str] = Field( copilot_mode: Optional[str] = Field(
None, 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 # Allow extra config fields
@ -107,7 +107,7 @@ class LLMModelConfigUpdate(BaseModel):
) )
copilot_mode: Optional[str] = Field( copilot_mode: Optional[str] = Field(
None, 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 # Allow extra config fields