mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-28 19:22:22 +02:00
feat(copilot): add copilot_mode field and config command tool
- Add `copilot_mode` field to LLM model configs API with "teaching" (diagnostics only) and "lab_assistant" (full configuration access) modes - Introduce new `ExecuteMultipleDeviceConfigCommands` tool for executing configuration commands on multiple devices - Include `tags` field in node data structure for enhanced project management - Update API documentation examples to reflect new `copilot_mode` field and context limit additions
This commit is contained in:
parent
8dc8facb13
commit
a777c36a29
@ -124,6 +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) |
|
||||||
| `is_default` | boolean | Set as default (default: false) |
|
| `is_default` | boolean | Set as default (default: false) |
|
||||||
|
|
||||||
**Important Notes:**
|
**Important Notes:**
|
||||||
@ -255,7 +256,8 @@ curl -X POST http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \
|
|||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"context_limit": 128,
|
"context_limit": 128,
|
||||||
"context_strategy": "balanced",
|
"context_strategy": "balanced",
|
||||||
"api_key": "sk-xxx"
|
"api_key": "sk-xxx",
|
||||||
|
"copilot_mode": null
|
||||||
},
|
},
|
||||||
"user_id": "uuid-user",
|
"user_id": "uuid-user",
|
||||||
"group_id": null,
|
"group_id": null,
|
||||||
@ -308,7 +310,8 @@ curl -X GET http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \
|
|||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"context_limit": 128,
|
"context_limit": 128,
|
||||||
"context_strategy": "balanced",
|
"context_strategy": "balanced",
|
||||||
"api_key": "sk-xxx"
|
"api_key": "sk-xxx",
|
||||||
|
"copilot_mode": "lab_assistant"
|
||||||
},
|
},
|
||||||
"user_id": "uuid-user",
|
"user_id": "uuid-user",
|
||||||
"group_id": null,
|
"group_id": null,
|
||||||
@ -330,7 +333,8 @@ curl -X GET http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \
|
|||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"context_limit": 200,
|
"context_limit": 200,
|
||||||
"context_strategy": "balanced",
|
"context_strategy": "balanced",
|
||||||
"api_key": null
|
"api_key": null,
|
||||||
|
"copilot_mode": null
|
||||||
},
|
},
|
||||||
"user_id": null,
|
"user_id": null,
|
||||||
"group_id": "uuid-group",
|
"group_id": "uuid-group",
|
||||||
@ -353,7 +357,8 @@ curl -X GET http://localhost:3080/v3/access/users/{user_id}/llm-model-configs \
|
|||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"context_limit": 128,
|
"context_limit": 128,
|
||||||
"context_strategy": "balanced",
|
"context_strategy": "balanced",
|
||||||
"api_key": "sk-xxx"
|
"api_key": "sk-xxx",
|
||||||
|
"copilot_mode": "lab_assistant"
|
||||||
},
|
},
|
||||||
"user_id": "uuid-user",
|
"user_id": "uuid-user",
|
||||||
"group_id": null,
|
"group_id": null,
|
||||||
@ -393,7 +398,10 @@ curl -X GET http://localhost:3080/v3/access/groups/{group_id}/llm-model-configs
|
|||||||
"base_url": "https://api.anthropic.com",
|
"base_url": "https://api.anthropic.com",
|
||||||
"model": "claude-3-opus-20240229",
|
"model": "claude-3-opus-20240229",
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"api_key": "sk-ant-xxx"
|
"context_limit": 200,
|
||||||
|
"context_strategy": "balanced",
|
||||||
|
"api_key": "sk-ant-xxx",
|
||||||
|
"copilot_mode": null
|
||||||
},
|
},
|
||||||
"user_id": null,
|
"user_id": null,
|
||||||
"group_id": "uuid-group",
|
"group_id": "uuid-group",
|
||||||
@ -411,7 +419,10 @@ curl -X GET http://localhost:3080/v3/access/groups/{group_id}/llm-model-configs
|
|||||||
"base_url": "https://api.openai.com/v1",
|
"base_url": "https://api.openai.com/v1",
|
||||||
"model": "gpt-4",
|
"model": "gpt-4",
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"api_key": "sk-xxx"
|
"context_limit": 128,
|
||||||
|
"context_strategy": "balanced",
|
||||||
|
"api_key": "sk-xxx",
|
||||||
|
"copilot_mode": null
|
||||||
},
|
},
|
||||||
"user_id": null,
|
"user_id": null,
|
||||||
"group_id": "uuid-group",
|
"group_id": "uuid-group",
|
||||||
|
|||||||
@ -65,6 +65,7 @@ from pathlib import Path
|
|||||||
# Add backend to path for prompt_manager
|
# Add backend to path for prompt_manager
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "backend"))
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "backend"))
|
||||||
from gns3server.agent.gns3_copilot.tools_v2 import (
|
from gns3server.agent.gns3_copilot.tools_v2 import (
|
||||||
|
ExecuteMultipleDeviceConfigCommands,
|
||||||
ExecuteMultipleDeviceCommands,
|
ExecuteMultipleDeviceCommands,
|
||||||
GNS3CreateNodeTool,
|
GNS3CreateNodeTool,
|
||||||
GNS3LinkTool,
|
GNS3LinkTool,
|
||||||
@ -88,6 +89,7 @@ tools = [
|
|||||||
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 on multiple devices (READ-ONLY)
|
||||||
|
ExecuteMultipleDeviceConfigCommands(), # Execute configuration commands on multiple devices
|
||||||
VPCSMultiCommands(), # Execute VPCS commands on multiple devices
|
VPCSMultiCommands(), # Execute VPCS commands on multiple devices
|
||||||
]
|
]
|
||||||
# Augment the LLM with tools
|
# Augment the LLM with tools
|
||||||
@ -197,7 +199,8 @@ def llm_call(state: dict, config: RunnableConfig | None = None):
|
|||||||
state["topology_info"] = topology_info
|
state["topology_info"] = topology_info
|
||||||
|
|
||||||
# Create pre_model_hook for automatic topology injection and trimming
|
# Create pre_model_hook for automatic topology injection and trimming
|
||||||
system_prompt = load_system_prompt()
|
# Load system prompt based on copilot_mode configuration
|
||||||
|
system_prompt = load_system_prompt(llm_config)
|
||||||
pre_hook = create_pre_model_hook(
|
pre_hook = create_pre_model_hook(
|
||||||
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"),
|
||||||
|
|||||||
@ -477,6 +477,11 @@ class Gns3Connector:
|
|||||||
**Required Attributes:**
|
**Required Attributes:**
|
||||||
|
|
||||||
- `name` or `template_id`
|
- `name` or `template_id`
|
||||||
|
|
||||||
|
**Optional Attributes (can be passed via kwargs):**
|
||||||
|
|
||||||
|
- `tags` (list): List of tags for the template (e.g., ["device_type:cisco_ios_telnet", "platform:cisco_ios"])
|
||||||
|
- Any other template attributes supported by GNS3 API
|
||||||
"""
|
"""
|
||||||
# Get existing template
|
# Get existing template
|
||||||
_template = self.get_template(name=name, template_id=template_id)
|
_template = self.get_template(name=name, template_id=template_id)
|
||||||
@ -505,6 +510,21 @@ class Gns3Connector:
|
|||||||
- `name`
|
- `name`
|
||||||
- `compute_id` by default is 'local'
|
- `compute_id` by default is 'local'
|
||||||
- `template_type`
|
- `template_type`
|
||||||
|
|
||||||
|
**Optional Attributes (can be passed via kwargs):**
|
||||||
|
|
||||||
|
- `tags` (list): List of tags for the template (e.g., ["device_type:cisco_ios_telnet", "platform:cisco_ios"])
|
||||||
|
- Any other template attributes supported by GNS3 API
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
>>> connector.create_template(
|
||||||
|
... name="cisco_router",
|
||||||
|
... template_type="dynamips",
|
||||||
|
... tags=["device_type:cisco_ios_telnet", "platform:cisco_ios"]
|
||||||
|
... )
|
||||||
|
```
|
||||||
"""
|
"""
|
||||||
# kwargs["name"] might raise KeyError at runtime, for more robust code we can use get first
|
# kwargs["name"] might raise KeyError at runtime, for more robust code we can use get first
|
||||||
template_name = kwargs.get("name")
|
template_name = kwargs.get("name")
|
||||||
@ -1051,6 +1071,7 @@ class Node:
|
|||||||
z: int | None = None
|
z: int | None = None
|
||||||
template_id: str | None = None
|
template_id: str | None = None
|
||||||
properties: Any | None = None
|
properties: Any | None = None
|
||||||
|
tags: list[str] | None = None
|
||||||
|
|
||||||
template: str | None = None
|
template: str | None = None
|
||||||
links: list[Link] = field(default_factory=list, repr=False)
|
links: list[Link] = field(default_factory=list, repr=False)
|
||||||
@ -2084,6 +2105,7 @@ class Project:
|
|||||||
# "template": _n.template,
|
# "template": _n.template,
|
||||||
"x": _n.x,
|
"x": _n.x,
|
||||||
"y": _n.y,
|
"y": _n.y,
|
||||||
|
"tags": _n.tags if _n.tags else [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -29,9 +29,14 @@ Prompts Module for GNS3-Copilot
|
|||||||
This package contains system prompts and prompt loading utilities for
|
This package contains system prompts and prompt loading utilities for
|
||||||
the GNS3-Copilot AI agent.
|
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)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .base_prompt import SYSTEM_PROMPT
|
from .base_prompt import SYSTEM_PROMPT
|
||||||
|
from .lab_assistant_prompt import LAB_ASSISTANT_PROMPT
|
||||||
from .prompt_loader import load_system_prompt
|
from .prompt_loader import load_system_prompt
|
||||||
from .title_prompt import TITLE_PROMPT
|
from .title_prompt import TITLE_PROMPT
|
||||||
|
|
||||||
@ -49,6 +54,7 @@ __url__ = "https://github.com/yueguobin/gns3-copilot"
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"SYSTEM_PROMPT",
|
"SYSTEM_PROMPT",
|
||||||
|
"LAB_ASSISTANT_PROMPT",
|
||||||
"TITLE_PROMPT",
|
"TITLE_PROMPT",
|
||||||
"load_system_prompt",
|
"load_system_prompt",
|
||||||
]
|
]
|
||||||
|
|||||||
182
gns3server/agent/gns3_copilot/prompts/lab_assistant_prompt.py
Normal file
182
gns3server/agent/gns3_copilot/prompts/lab_assistant_prompt.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# GNS3-Copilot - AI-powered Network Lab Assistant for GNS3
|
||||||
|
#
|
||||||
|
# This file is part of GNS3-Copilot project.
|
||||||
|
#
|
||||||
|
# GNS3-Copilot is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the
|
||||||
|
# Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
# option) any later version.
|
||||||
|
#
|
||||||
|
# GNS3-Copilot is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
# for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with GNS3-Copilot. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2025 Guobin Yue
|
||||||
|
# Author: Guobin Yue
|
||||||
|
#
|
||||||
|
# Project Home: https://github.com/yueguobin/gns3-copilot
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
System Prompt for GNS3 Lab Assistant
|
||||||
|
|
||||||
|
This module contains the system prompt for the GNS3 Lab Assistant,
|
||||||
|
an AI-powered assistant that can perform both diagnostic AND configuration
|
||||||
|
operations in GNS3 network labs.
|
||||||
|
|
||||||
|
This is the full-featured assistant mode with configuration permissions enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# System prompt for GNS3 Lab Assistant
|
||||||
|
LAB_ASSISTANT_PROMPT = """
|
||||||
|
# ROLE & MISSION
|
||||||
|
|
||||||
|
You are a **GNS3 Network Lab Assistant** - an intelligent automation assistant
|
||||||
|
designed to help users manage, diagnose, and configure network devices in GNS3
|
||||||
|
virtual laboratories.
|
||||||
|
|
||||||
|
**Your Mission**: Understand user intent and use available tools to efficiently
|
||||||
|
complete network operations, including both diagnostics and configurations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# AVAILABLE TOOLS
|
||||||
|
|
||||||
|
You have access to the following tools to help users:
|
||||||
|
|
||||||
|
| Tool | Purpose | Usage |
|
||||||
|
|------|---------|-------|
|
||||||
|
| `gns3_template_reader` | Get available node templates | List templates for creating nodes |
|
||||||
|
| `gns3_create_node` | Create new nodes in topology | Add routers, switches, VPCS, etc. |
|
||||||
|
| `gns3_link_tool` | Create links between nodes | Connect network topology |
|
||||||
|
| `gns3_start_node_tool` | Start/stop nodes | Control device power state |
|
||||||
|
| `gns3_update_node_name_tool` | Update node names | Rename devices |
|
||||||
|
| `execute_multiple_device_commands` | Execute display commands | Read-only diagnostics (show/display/debug) |
|
||||||
|
| `execute_multiple_device_config_commands` | Execute configuration commands | Make configuration changes |
|
||||||
|
| `vpcs_multi_commands` | Execute VPCS commands | Configure VPCS devices |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# TOOL USAGE RULES
|
||||||
|
|
||||||
|
1. **Sequential Execution**: Call ONE tool at a time, wait for results before calling next
|
||||||
|
2. **Topology Awareness**: If topology is already in context, DO NOT call topology reader again
|
||||||
|
3. **Efficient Operations**: Batch commands for multiple devices when possible
|
||||||
|
4. **Safety First**: Be cautious with destructive operations (reload, erase, format)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# WORKFLOW GUIDELINES
|
||||||
|
|
||||||
|
## Step 1: Understand Intent
|
||||||
|
Analyze what the user wants to accomplish:
|
||||||
|
- Diagnosis? → Use `execute_multiple_device_commands`
|
||||||
|
- Configuration? → Use `execute_multiple_device_config_commands`
|
||||||
|
- Topology changes? → Use node/link management tools
|
||||||
|
|
||||||
|
## Step 2: Plan & Execute
|
||||||
|
1. Check current topology context
|
||||||
|
2. Use appropriate tool(s) for the task
|
||||||
|
3. Verify results when necessary
|
||||||
|
4. Provide clear feedback to user
|
||||||
|
|
||||||
|
## Step 3: Report Results
|
||||||
|
Clearly communicate:
|
||||||
|
- What was done
|
||||||
|
- Results (success/failure)
|
||||||
|
- Any errors encountered
|
||||||
|
- Next steps or recommendations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# COMMAND EXAMPLES
|
||||||
|
|
||||||
|
## Diagnostic Commands (READ-ONLY)
|
||||||
|
```
|
||||||
|
# Cisco IOS
|
||||||
|
show version
|
||||||
|
show running-config
|
||||||
|
show ip interface brief
|
||||||
|
show ip route
|
||||||
|
show ip ospf neighbor
|
||||||
|
show ip bgp summary
|
||||||
|
debug ip routing
|
||||||
|
|
||||||
|
# Huawei VRP
|
||||||
|
display current-configuration
|
||||||
|
display ip routing-table
|
||||||
|
display ospf peer
|
||||||
|
display bgp peer
|
||||||
|
|
||||||
|
# Verification
|
||||||
|
ping 192.168.1.1
|
||||||
|
traceroute 10.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Commands
|
||||||
|
```
|
||||||
|
# Interface Configuration
|
||||||
|
interface GigabitEthernet0/0
|
||||||
|
ip address 192.168.1.1 255.255.255.0
|
||||||
|
no shutdown
|
||||||
|
|
||||||
|
# Routing Configuration
|
||||||
|
router ospf 1
|
||||||
|
network 192.168.1.0 0.0.0.255 area 0
|
||||||
|
|
||||||
|
# VLAN Configuration
|
||||||
|
vlan 10
|
||||||
|
name Sales
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# RESPONSE GUIDELINES
|
||||||
|
|
||||||
|
1. **Language Matching**:
|
||||||
|
- User writes in Chinese → Respond in Chinese
|
||||||
|
- User writes in English → Respond in English
|
||||||
|
- Keep technical terms in English (OSPF, BGP, VLAN, CLI commands)
|
||||||
|
|
||||||
|
2. **Clear Structure**:
|
||||||
|
```markdown
|
||||||
|
## 操作总结 / Operation Summary
|
||||||
|
|
||||||
|
**执行的任务**: [What was done]
|
||||||
|
**结果**: [Success/Failure]
|
||||||
|
|
||||||
|
## 详细信息 / Details
|
||||||
|
[Device outputs, configurations, etc.]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Error Handling**:
|
||||||
|
- Report errors clearly
|
||||||
|
- Suggest troubleshooting steps
|
||||||
|
- Offer to retry or investigate further
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# SAFETY REMINDERS
|
||||||
|
|
||||||
|
While you have configuration permissions, exercise caution:
|
||||||
|
- ⚠️ Avoid destructive commands (reload, erase, format) unless explicitly requested
|
||||||
|
- ⚠️ Warn user before making major changes
|
||||||
|
- ⚠️ Recommend backup for critical configurations
|
||||||
|
- ⚠️ Verify connectivity before routing protocol changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# CURRENT TOPOLOGY
|
||||||
|
|
||||||
|
{{topology_info}}
|
||||||
|
|
||||||
|
**Note**: Topology is already retrieved. DO NOT call topology reader again unless needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
"""
|
||||||
@ -27,29 +27,45 @@
|
|||||||
Prompt Loader for GNS3-Copilot
|
Prompt Loader for GNS3-Copilot
|
||||||
|
|
||||||
This module provides utilities for loading system prompts.
|
This module provides utilities for loading system prompts.
|
||||||
Can be extended to support multiple prompt variants based on
|
Supports multiple prompt variants based on LLM model configuration.
|
||||||
environment variables (e.g., ENGLISH_LEVEL).
|
|
||||||
|
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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from .base_prompt import SYSTEM_PROMPT
|
from .base_prompt import SYSTEM_PROMPT
|
||||||
|
from .lab_assistant_prompt import LAB_ASSISTANT_PROMPT
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def load_system_prompt() -> str:
|
def load_system_prompt(llm_config: dict | None = None) -> str:
|
||||||
"""
|
"""
|
||||||
Load the system prompt for GNS3-Copilot.
|
Load the system prompt for GNS3-Copilot.
|
||||||
|
|
||||||
In the future, this can be extended to support multiple prompt variants
|
The prompt mode is controlled by the `copilot_mode` field in the LLM model config:
|
||||||
based on environment variables (e.g., ENGLISH_LEVEL).
|
- "teaching" (default): Teaching assistant mode - diagnostics only, no configuration
|
||||||
|
- "lab_assistant": Full lab assistant mode - diagnostics and configuration enabled
|
||||||
|
|
||||||
|
Args:
|
||||||
|
llm_config: LLM model configuration dictionary containing the config field
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The system prompt string.
|
str: The system prompt string.
|
||||||
"""
|
"""
|
||||||
# For now, just return the base system prompt
|
if not llm_config:
|
||||||
# Future enhancement: Load different prompts based on ENGLISH_LEVEL env var
|
logger.info("No LLM config provided, using default TEACHING assistant prompt mode")
|
||||||
# english_level = os.getenv("ENGLISH_LEVEL", "native")
|
return SYSTEM_PROMPT
|
||||||
return SYSTEM_PROMPT
|
|
||||||
|
config = llm_config.get("config", {})
|
||||||
|
mode = config.get("copilot_mode", "teaching").lower()
|
||||||
|
|
||||||
|
if mode == "lab_assistant":
|
||||||
|
logger.info("Using LAB_ASSISTANT prompt mode (diagnostics + configuration)")
|
||||||
|
return LAB_ASSISTANT_PROMPT
|
||||||
|
else:
|
||||||
|
logger.info("Using TEACHING assistant prompt mode (diagnostics only)")
|
||||||
|
return SYSTEM_PROMPT
|
||||||
|
|||||||
@ -53,25 +53,52 @@ def _get_nornir_defaults() -> dict[str, Any]:
|
|||||||
"""Get Nornir default configuration for Cisco IOS."""
|
"""Get Nornir default configuration for Cisco IOS."""
|
||||||
return {"data": {"location": "gns3"}}
|
return {"data": {"location": "gns3"}}
|
||||||
|
|
||||||
def _get_nornir_groups_config() -> dict[str, dict[str, Any]]:
|
def _get_nornir_groups_config(
|
||||||
"""Get Nornir groups configuration for Cisco IOS Telnet devices."""
|
device_type: str = "cisco_ios_telnet",
|
||||||
|
platform: str = "cisco_ios"
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get Nornir group configuration for Cisco IOS Telnet devices.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_type: Device type for Netmiko (e.g., 'cisco_ios_telnet')
|
||||||
|
platform: Platform type for Nornir (e.g., 'cisco_ios')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing Nornir group configuration
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
"cisco_IOSv_telnet": {
|
"platform": platform,
|
||||||
"platform": "cisco_ios",
|
"hostname": get_gns3_server_host(),
|
||||||
"hostname": get_gns3_server_host(),
|
"timeout": 120,
|
||||||
"timeout": 120,
|
"username": "",
|
||||||
"username": "",
|
"password": "",
|
||||||
"password": "",
|
"connection_options": {
|
||||||
"connection_options": {
|
"netmiko": {"extras": {"device_type": device_type}}
|
||||||
"netmiko": {"extras": {"device_type": "cisco_ios_telnet"}}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_nornir_group(group_name: str = "cisco_IOSv_telnet") -> dict[str, Any]:
|
def _get_nornir_group(
|
||||||
"""Get Nornir group configuration for a specific group."""
|
group_name: str = "cisco_IOSv_telnet",
|
||||||
all_groups = _get_nornir_groups_config()
|
device_type: str = "cisco_ios_telnet",
|
||||||
return all_groups.get(group_name, {})
|
platform: str = "cisco_ios"
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get Nornir group configuration for a specific group.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group_name: Name of the group
|
||||||
|
device_type: Device type for Netmiko (e.g., 'cisco_ios_telnet')
|
||||||
|
platform: Platform type for Nornir (e.g., 'cisco_ios')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing group configuration
|
||||||
|
"""
|
||||||
|
# _get_nornir_groups_config now returns the group config directly
|
||||||
|
return _get_nornir_groups_config(
|
||||||
|
device_type=device_type,
|
||||||
|
platform=platform
|
||||||
|
)
|
||||||
|
|
||||||
class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
||||||
"""
|
"""
|
||||||
@ -389,8 +416,27 @@ class ExecuteMultipleDeviceConfigCommands(BaseTool):
|
|||||||
def _initialize_nornir(self, hosts_data: dict[str, dict[str, Any]]) -> Nornir:
|
def _initialize_nornir(self, hosts_data: dict[str, dict[str, Any]]) -> Nornir:
|
||||||
"""Initialize Nornir with the provided hosts data."""
|
"""Initialize Nornir with the provided hosts data."""
|
||||||
try:
|
try:
|
||||||
# Get latest environment configuration
|
# Extract device_type and platform from hosts_data
|
||||||
groups_data = _get_nornir_group("cisco_IOSv_telnet")
|
# Use the first device's configuration as default
|
||||||
|
device_type = None
|
||||||
|
platform = None
|
||||||
|
|
||||||
|
if hosts_data:
|
||||||
|
first_device_data = next(iter(hosts_data.values()), {})
|
||||||
|
device_type = first_device_data.get("device_type", "cisco_ios_telnet")
|
||||||
|
platform = first_device_data.get("platform", "cisco_ios")
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Extracted from tags: device_type=%s, platform=%s",
|
||||||
|
device_type,
|
||||||
|
platform
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get latest environment configuration with dynamic device_type and platform
|
||||||
|
groups_data = _get_nornir_groups_config(
|
||||||
|
device_type=device_type or "cisco_ios_telnet",
|
||||||
|
platform=platform or "cisco_ios"
|
||||||
|
)
|
||||||
defaults = _get_nornir_defaults()
|
defaults = _get_nornir_defaults()
|
||||||
|
|
||||||
# Log nornir account information
|
# Log nornir account information
|
||||||
|
|||||||
@ -54,25 +54,52 @@ def _get_nornir_defaults() -> dict[str, Any]:
|
|||||||
"""Get Nornir default configuration for Cisco IOS."""
|
"""Get Nornir default configuration for Cisco IOS."""
|
||||||
return {"data": {"location": "gns3"}}
|
return {"data": {"location": "gns3"}}
|
||||||
|
|
||||||
def _get_nornir_groups_config() -> dict[str, dict[str, Any]]:
|
def _get_nornir_groups_config(
|
||||||
"""Get Nornir groups configuration for Cisco IOS Telnet devices."""
|
device_type: str = "cisco_ios_telnet",
|
||||||
|
platform: str = "cisco_ios"
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get Nornir group configuration for Cisco IOS Telnet devices.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
device_type: Device type for Netmiko (e.g., 'cisco_ios_telnet')
|
||||||
|
platform: Platform type for Nornir (e.g., 'cisco_ios')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing Nornir group configuration
|
||||||
|
"""
|
||||||
return {
|
return {
|
||||||
"cisco_IOSv_telnet": {
|
"platform": platform,
|
||||||
"platform": "cisco_ios",
|
"hostname": get_gns3_server_host(),
|
||||||
"hostname": get_gns3_server_host(),
|
"timeout": 120,
|
||||||
"timeout": 120,
|
"username": "",
|
||||||
"username": "",
|
"password": "",
|
||||||
"password": "",
|
"connection_options": {
|
||||||
"connection_options": {
|
"netmiko": {"extras": {"device_type": device_type}}
|
||||||
"netmiko": {"extras": {"device_type": "cisco_ios_telnet"}}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_nornir_group(group_name: str = "cisco_IOSv_telnet") -> dict[str, Any]:
|
def _get_nornir_group(
|
||||||
"""Get Nornir group configuration for a specific group."""
|
group_name: str = "cisco_IOSv_telnet",
|
||||||
all_groups = _get_nornir_groups_config()
|
device_type: str = "cisco_ios_telnet",
|
||||||
return all_groups.get(group_name, {})
|
platform: str = "cisco_ios"
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get Nornir group configuration for a specific group.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group_name: Name of the group
|
||||||
|
device_type: Device type for Netmiko (e.g., 'cisco_ios_telnet')
|
||||||
|
platform: Platform type for Nornir (e.g., 'cisco_ios')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing group configuration
|
||||||
|
"""
|
||||||
|
# _get_nornir_groups_config now returns the group config directly
|
||||||
|
return _get_nornir_groups_config(
|
||||||
|
device_type=device_type,
|
||||||
|
platform=platform
|
||||||
|
)
|
||||||
|
|
||||||
class ExecuteMultipleDeviceCommands(BaseTool):
|
class ExecuteMultipleDeviceCommands(BaseTool):
|
||||||
"""
|
"""
|
||||||
@ -403,8 +430,27 @@ class ExecuteMultipleDeviceCommands(BaseTool):
|
|||||||
def _initialize_nornir(self, hosts_data: dict[str, dict[str, Any]]) -> Nornir:
|
def _initialize_nornir(self, hosts_data: dict[str, dict[str, Any]]) -> Nornir:
|
||||||
"""Initialize Nornir with the provided hosts data."""
|
"""Initialize Nornir with the provided hosts data."""
|
||||||
try:
|
try:
|
||||||
# Get latest environment configuration
|
# Extract device_type and platform from hosts_data
|
||||||
groups_data = _get_nornir_group("cisco_IOSv_telnet")
|
# Use the first device's configuration as default
|
||||||
|
device_type = None
|
||||||
|
platform = None
|
||||||
|
|
||||||
|
if hosts_data:
|
||||||
|
first_device_data = next(iter(hosts_data.values()), {})
|
||||||
|
device_type = first_device_data.get("device_type", "cisco_ios_telnet")
|
||||||
|
platform = first_device_data.get("platform", "cisco_ios")
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Extracted from tags: device_type=%s, platform=%s",
|
||||||
|
device_type,
|
||||||
|
platform
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get latest environment configuration with dynamic device_type and platform
|
||||||
|
groups_data = _get_nornir_groups_config(
|
||||||
|
device_type=device_type or "cisco_ios_telnet",
|
||||||
|
platform=platform or "cisco_ios"
|
||||||
|
)
|
||||||
defaults = _get_nornir_defaults()
|
defaults = _get_nornir_defaults()
|
||||||
|
|
||||||
# Log nornir account information
|
# Log nornir account information
|
||||||
|
|||||||
@ -48,7 +48,9 @@ def get_device_ports_from_topology(
|
|||||||
{
|
{
|
||||||
"device_name": {
|
"device_name": {
|
||||||
"port": console_port,
|
"port": console_port,
|
||||||
"groups": ["cisco_IOSv_telnet"]
|
"groups": ["cisco_IOSv_telnet"],
|
||||||
|
"device_type": "cisco_ios_telnet", # Extracted from tags
|
||||||
|
"platform": "cisco_ios" # Extracted from tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Devices that don't exist or missing console_port will not be included
|
Devices that don't exist or missing console_port will not be included
|
||||||
@ -82,10 +84,36 @@ def get_device_ports_from_topology(
|
|||||||
logger.warning("Device '%s' missing console_port", device_name)
|
logger.warning("Device '%s' missing console_port", device_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Extract device_type and platform from tags
|
||||||
|
device_type = None
|
||||||
|
platform = None
|
||||||
|
tags = node_info.get("tags", [])
|
||||||
|
|
||||||
|
for tag in tags:
|
||||||
|
if tag.startswith("device_type:"):
|
||||||
|
device_type = tag.split(":", 1)[1].strip()
|
||||||
|
elif tag.startswith("platform:"):
|
||||||
|
platform = tag.split(":", 1)[1].strip()
|
||||||
|
|
||||||
|
# Use defaults if not found in tags
|
||||||
|
if device_type is None:
|
||||||
|
device_type = "cisco_ios_telnet"
|
||||||
|
logger.debug("Device '%s': device_type not found in tags, using default: cisco_ios_telnet", device_name)
|
||||||
|
else:
|
||||||
|
logger.debug("Device '%s': extracted device_type=%s from tags", device_name, device_type)
|
||||||
|
|
||||||
|
if platform is None:
|
||||||
|
platform = "cisco_ios"
|
||||||
|
logger.debug("Device '%s': platform not found in tags, using default: cisco_ios", device_name)
|
||||||
|
else:
|
||||||
|
logger.debug("Device '%s': extracted platform=%s from tags", device_name, platform)
|
||||||
|
|
||||||
# Add device to hosts_data
|
# Add device to hosts_data
|
||||||
hosts_data[device_name] = {
|
hosts_data[device_name] = {
|
||||||
"port": node_info["console_port"],
|
"port": node_info["console_port"],
|
||||||
"groups": ["cisco_IOSv_telnet"],
|
"groups": ["cisco_IOSv_telnet"],
|
||||||
|
"device_type": device_type,
|
||||||
|
"platform": platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Returning %d device port mappings", len(hosts_data))
|
logger.info("Returning %d device port mappings", len(hosts_data))
|
||||||
|
|||||||
@ -48,9 +48,14 @@ class LLMModelConfigData(BaseModel):
|
|||||||
context_strategy: Literal["conservative", "balanced", "aggressive"] = Field(
|
context_strategy: Literal["conservative", "balanced", "aggressive"] = Field(
|
||||||
"balanced", description="Context trimming strategy: conservative (60%), balanced (75%), aggressive (85%)"
|
"balanced", description="Context trimming strategy: conservative (60%), balanced (75%), aggressive (85%)"
|
||||||
)
|
)
|
||||||
|
copilot_mode: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description="GNS3-Copilot mode: 'teaching' (diagnostics only) or 'lab_assistant' (full configuration access)"
|
||||||
|
)
|
||||||
|
|
||||||
# Allow extra fields for extensibility
|
# Allow extra fields for extensibility
|
||||||
model_config = ConfigDict(extra="allow")
|
# Ensure all fields are included in serialization, even if None
|
||||||
|
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||||
|
|
||||||
|
|
||||||
# Request schemas
|
# Request schemas
|
||||||
@ -71,6 +76,10 @@ class LLMModelConfigCreate(BaseModel):
|
|||||||
context_strategy: Literal["conservative", "balanced", "aggressive"] = Field(
|
context_strategy: Literal["conservative", "balanced", "aggressive"] = Field(
|
||||||
"balanced", description="Context trimming strategy"
|
"balanced", description="Context trimming strategy"
|
||||||
)
|
)
|
||||||
|
copilot_mode: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description="GNS3-Copilot mode: 'teaching' (diagnostics only) or 'lab_assistant' (full configuration access)"
|
||||||
|
)
|
||||||
|
|
||||||
# Allow extra config fields
|
# Allow extra config fields
|
||||||
model_config = ConfigDict(extra="allow")
|
model_config = ConfigDict(extra="allow")
|
||||||
@ -96,6 +105,10 @@ class LLMModelConfigUpdate(BaseModel):
|
|||||||
context_strategy: Optional[Literal["conservative", "balanced", "aggressive"]] = Field(
|
context_strategy: Optional[Literal["conservative", "balanced", "aggressive"]] = Field(
|
||||||
None, description="Context trimming strategy"
|
None, description="Context trimming strategy"
|
||||||
)
|
)
|
||||||
|
copilot_mode: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description="GNS3-Copilot mode: 'teaching' (diagnostics only) or 'lab_assistant' (full configuration access)"
|
||||||
|
)
|
||||||
|
|
||||||
# Allow extra config fields
|
# Allow extra config fields
|
||||||
model_config = ConfigDict(extra="allow")
|
model_config = ConfigDict(extra="allow")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user