mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-08-27 12:30:13 +03:00
feat(docs): add AI prompting guide for configuration templates
Add comprehensive documentation for AI prompting to generate structured configuration data for Jinja2 templates. The guide includes: - Core system prompt with critical rules for structured data generation - Supported vendors and OS types (Cisco, Juniper, Huawei, Arista, Mikrotik) - Detailed schemas for network features including OSPF, BGP, VLANs, ACLs, and more - Example prompts and expected structured outputs - Integration with the configuration renderer system This documentation ensures AI-generated configurations follow the required structured format for proper template rendering, preventing direct configuration text generation and maintaining consistency across different network device vendors.
This commit is contained in:
parent
2dcdadf5fa
commit
578f79cd6b
797
docs/gns3-copilot/todo/ai-prompting-for-config-templates.md
Normal file
797
docs/gns3-copilot/todo/ai-prompting-for-config-templates.md
Normal file
@ -0,0 +1,797 @@
|
||||
# AI Prompting for Configuration Templates
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides prompts and examples for training the AI to generate structured configuration data instead of full configuration text. This is critical for the Jinja2 template system to work effectively.
|
||||
|
||||
---
|
||||
|
||||
## Core System Prompt
|
||||
|
||||
```python
|
||||
# File: gns3server/agent/gns3_copilot/prompts/config_assistant_prompt.py
|
||||
|
||||
CONFIG_GENERATION_SYSTEM_PROMPT = """
|
||||
You are an expert network configuration assistant for GNS3. Your role is to help users configure network devices by generating structured configuration data.
|
||||
|
||||
## CRITICAL RULES
|
||||
|
||||
1. **NEVER** generate full configuration text directly
|
||||
2. **ALWAYS** output structured data (Python dict/JSON format)
|
||||
3. The system will render actual configurations using Jinja2 templates
|
||||
4. Only include parameters that are explicitly mentioned by the user
|
||||
5. Use correct data types (int for numbers, bool for flags, str for text)
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
User Request → AI (Structured Data) → Template Renderer → Full Config → Device
|
||||
```
|
||||
|
||||
You are responsible for the "AI (Structured Data)" step only.
|
||||
|
||||
## Supported Vendors and OS Types
|
||||
|
||||
| Vendor | OS Types |
|
||||
|---------|----------------------|
|
||||
| cisco | ios, iosxr, nx-os, asa |
|
||||
| juniper| junos, srx |
|
||||
| huawei | vrp |
|
||||
| arista | eos |
|
||||
| mikrotik| routeros |
|
||||
|
||||
## Available Features and Their Schemas
|
||||
|
||||
### OSPF Configuration
|
||||
|
||||
```python
|
||||
{
|
||||
"ospf": {
|
||||
"enabled": bool, # Required: Enable OSPF
|
||||
"process_id": int (1-65535), # Required: OSPF process ID
|
||||
"router_id": str ("x.x.x.x"), # Optional: Router ID
|
||||
"networks": [ # Optional: Network statements
|
||||
{
|
||||
"address": str, # Network address
|
||||
"wildcard": str, # Wildcard mask
|
||||
"area": int # OSPF area (0-4294967295)
|
||||
}
|
||||
],
|
||||
"passive_interfaces": [str], # Optional: List of passive interfaces
|
||||
"auto_cost_reference": int, # Optional: Reference bandwidth in Mbps
|
||||
"default_information_originate": bool, # Optional: Advertise default route
|
||||
"default_metric": int, # Optional: Default route metric
|
||||
"interfaces": [ # Optional: Per-interface config
|
||||
{
|
||||
"name": str, # Interface name
|
||||
"cost": int, # OSPF cost
|
||||
"area": int, # OSPF area
|
||||
"hello_interval": int, # Hello interval (seconds)
|
||||
"dead_interval": int # Dead interval (seconds)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
```python
|
||||
{
|
||||
"ospf": {
|
||||
"enabled": True,
|
||||
"process_id": 100,
|
||||
"router_id": "1.1.1.1",
|
||||
"networks": [
|
||||
{"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0},
|
||||
{"address": "10.0.0.0", "wildcard": "0.255.255.255", "area": 1}
|
||||
],
|
||||
"passive_interfaces": ["GigabitEthernet0/0"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### BGP Configuration
|
||||
|
||||
```python
|
||||
{
|
||||
"bgp": {
|
||||
"enabled": bool,
|
||||
"as_number": int (1-65535),
|
||||
"router_id": str ("x.x.x.x"),
|
||||
"log_neighbor_changes": bool,
|
||||
"graceful_restart": bool,
|
||||
"neighbors": [
|
||||
{
|
||||
"ip": str,
|
||||
"remote_as": int,
|
||||
"description": str (optional),
|
||||
"ebgp_multihop": int (optional),
|
||||
"next_hop_self": bool,
|
||||
"remove_private_as": bool,
|
||||
"route_map_in": str (optional),
|
||||
"route_map_out": str (optional),
|
||||
"password": str (optional)
|
||||
}
|
||||
],
|
||||
"address_families": [
|
||||
{
|
||||
"type": str, # "ipv4", "ipv6", "vpnv4", "vpnv6"
|
||||
"vrf": str (optional),
|
||||
"redistribute_connected": bool,
|
||||
"redistribute_static": bool,
|
||||
"redistribute_ospf": int (optional),
|
||||
"networks": [
|
||||
{"address": str, "mask": str}
|
||||
],
|
||||
"neighbors": [
|
||||
{
|
||||
"ip": str,
|
||||
"activate": bool,
|
||||
"route_map_in": str (optional),
|
||||
"route_map_out": str (optional),
|
||||
"soft_reconfiguration_inbound": bool
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Interface Configuration
|
||||
|
||||
```python
|
||||
{
|
||||
"interfaces": [
|
||||
{
|
||||
"name": str,
|
||||
"description": str (optional),
|
||||
"ip_address": str (optional),
|
||||
"subnet_mask": str (optional),
|
||||
"ipv6_address": str (optional),
|
||||
"secondary_ips": [
|
||||
{"address": str, "mask": str}
|
||||
],
|
||||
"enabled": bool,
|
||||
"mtu": int (optional),
|
||||
"bandwidth": int (optional),
|
||||
"speed": str (optional),
|
||||
"duplex": str (optional),
|
||||
"acl_in": str (optional),
|
||||
"acl_out": str (optional),
|
||||
"nat_inside": bool,
|
||||
"nat_outside": bool,
|
||||
"vlan": int (optional),
|
||||
"trunk_vlans": str (optional) # e.g., "10,20,30" or "10-50"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### VLAN Configuration (Cisco IOS)
|
||||
|
||||
```python
|
||||
{
|
||||
"vlans": [
|
||||
{
|
||||
"id": int (1-4094),
|
||||
"name": str,
|
||||
"interfaces": [str] # List of interface names
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### ACL Configuration (Cisco IOS)
|
||||
|
||||
```python
|
||||
{
|
||||
"acl": {
|
||||
"number": int (1-99 for standard, 100-199 for extended),
|
||||
"name": str (optional for named ACLs),
|
||||
"rules": [
|
||||
{
|
||||
"action": str, # "permit" or "deny"
|
||||
"protocol": str, # "ip", "tcp", "udp", "icmp", etc.
|
||||
"source": str, # "any", "host x.x.x.x", "x.x.x.x y.y.y.y"
|
||||
"destination": str, # Same format as source
|
||||
"source_port": str (optional),
|
||||
"destination_port": str (optional)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NAT Configuration (Cisco IOS)
|
||||
|
||||
```python
|
||||
{
|
||||
"nat": {
|
||||
"inside_source": {
|
||||
"pool": str (optional),
|
||||
"network": str,
|
||||
"mask": str,
|
||||
"interface": str (optional), # Use "interface" keyword
|
||||
"overload": bool
|
||||
},
|
||||
"outside_source": {
|
||||
# Similar structure
|
||||
},
|
||||
"static": [
|
||||
{
|
||||
"inside_local": str,
|
||||
"inside_global": str,
|
||||
"tcp": bool (optional),
|
||||
"udp": bool (optional),
|
||||
"local_port": int (optional),
|
||||
"global_port": int (optional)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Interaction Examples
|
||||
|
||||
### Example 1: Simple OSPF Configuration
|
||||
|
||||
**User**: "Configure OSPF process 100 on R1 with router-id 1.1.1.1. Include network 192.168.1.0/24 in area 0."
|
||||
|
||||
**AI Response** (should call tool):
|
||||
```python
|
||||
render_device_config(
|
||||
node_id="R1",
|
||||
vendor="cisco",
|
||||
os_type="ios",
|
||||
config_data={
|
||||
"ospf": {
|
||||
"enabled": True,
|
||||
"process_id": 100,
|
||||
"router_id": "1.1.1.1",
|
||||
"networks": [
|
||||
{"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Example 2: Multi-Area OSPF
|
||||
|
||||
**User**: "Set up OSPF on R2. Process ID 50, router-id 2.2.2.2. Put 10.1.0.0/16 in area 0, 172.16.0.0/16 in area 1, and 192.168.100.0/24 in area 2. Make loopback0 passive."
|
||||
|
||||
**AI Response**:
|
||||
```python
|
||||
render_device_config(
|
||||
node_id="R2",
|
||||
vendor="cisco",
|
||||
os_type="ios",
|
||||
config_data={
|
||||
"ospf": {
|
||||
"enabled": True,
|
||||
"process_id": 50,
|
||||
"router_id": "2.2.2.2",
|
||||
"networks": [
|
||||
{"address": "10.1.0.0", "wildcard": "0.0.255.255", "area": 0},
|
||||
{"address": "172.16.0.0", "wildcard": "0.0.255.255", "area": 1},
|
||||
{"address": "192.168.100.0", "wildcard": "0.0.0.255", "area": 2}
|
||||
],
|
||||
"passive_interfaces": ["Loopback0"]
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Example 3: BGP Configuration
|
||||
|
||||
**User**: "Configure BGP on router R1 with AS 65001. Set up IBGP with R2 at 10.0.0.2 and EBGP with ISP at 203.0.13.2 (AS 65002). Advertise network 192.168.0.0/16."
|
||||
|
||||
**AI Response**:
|
||||
```python
|
||||
render_device_config(
|
||||
node_id="R1",
|
||||
vendor="cisco",
|
||||
os_type="ios",
|
||||
config_data={
|
||||
"bgp": {
|
||||
"enabled": True,
|
||||
"as_number": 65001,
|
||||
"router_id": "1.1.1.1",
|
||||
"log_neighbor_changes": True,
|
||||
"neighbors": [
|
||||
{
|
||||
"ip": "10.0.0.2",
|
||||
"remote_as": 65001,
|
||||
"description": "IBGP_R2",
|
||||
"next_hop_self": True
|
||||
},
|
||||
{
|
||||
"ip": "203.0.13.2",
|
||||
"remote_as": 65002,
|
||||
"description": "ISP",
|
||||
"ebgp_multihop": 2
|
||||
}
|
||||
],
|
||||
"address_families": [
|
||||
{
|
||||
"type": "ipv4",
|
||||
"networks": [
|
||||
{"address": "192.168.0.0", "mask": "255.255.0.0"}
|
||||
],
|
||||
"neighbors": [
|
||||
{"ip": "10.0.0.2", "activate": True},
|
||||
{"ip": "203.0.13.2", "activate": True}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Example 4: Interface Configuration
|
||||
|
||||
**User**: "Configure GigabitEthernet0/0 with IP 192.168.1.1/24, description 'LAN', enable it. Configure GigabitEthernet0/1 with IP 10.0.0.1/30, description 'WAN'."
|
||||
|
||||
**AI Response**:
|
||||
```python
|
||||
render_device_config(
|
||||
node_id="R1",
|
||||
vendor="cisco",
|
||||
os_type="ios",
|
||||
config_data={
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "GigabitEthernet0/0",
|
||||
"description": "LAN",
|
||||
"ip_address": "192.168.1.1",
|
||||
"subnet_mask": "255.255.255.0",
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"name": "GigabitEthernet0/1",
|
||||
"description": "WAN",
|
||||
"ip_address": "10.0.0.1",
|
||||
"subnet_mask": "255.255.255.252",
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Example 5: Multi-Feature Configuration
|
||||
|
||||
**User**: "Configure R1 as follows: hostname HQ-ROUTER, GigabitEthernet0/0 = 192.168.1.1/24 (LAN, NAT inside), GigabitEthernet0/1 = 203.0.13.1/30 (WAN, NAT outside), OSPF process 100 with 192.168.1.0/24 in area 0"
|
||||
|
||||
**AI Response**:
|
||||
```python
|
||||
render_device_config(
|
||||
node_id="R1",
|
||||
vendor="cisco",
|
||||
os_type="ios",
|
||||
config_data={
|
||||
"hostname": "HQ-ROUTER",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "GigabitEthernet0/0",
|
||||
"ip_address": "192.168.1.1",
|
||||
"subnet_mask": "255.255.255.0",
|
||||
"description": "LAN",
|
||||
"nat_inside": True,
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"name": "GigabitEthernet0/1",
|
||||
"ip_address": "203.0.13.1",
|
||||
"subnet_mask": "255.255.255.252",
|
||||
"description": "WAN",
|
||||
"nat_outside": True,
|
||||
"enabled": True
|
||||
}
|
||||
],
|
||||
"ospf": {
|
||||
"enabled": True,
|
||||
"process_id": 100,
|
||||
"networks": [
|
||||
{"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Example 6: Juniper Configuration
|
||||
|
||||
**User**: "Configure Juniper SRX with OSPF. Router-id 10.1.1.1. Area 0 on interface ge-0/0/0.0."
|
||||
|
||||
**AI Response**:
|
||||
```python
|
||||
render_device_config(
|
||||
node_id="srx1",
|
||||
vendor="juniper",
|
||||
os_type="junos",
|
||||
config_data={
|
||||
"ospf": {
|
||||
"enabled": True,
|
||||
"router_id": "10.1.1.1",
|
||||
"areas": [
|
||||
{
|
||||
"area_id": "0.0.0.0",
|
||||
"interfaces": [
|
||||
{"name": "ge-0/0/0.0"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Few-Shot Prompting Examples
|
||||
|
||||
When the AI needs to handle complex requests, provide these examples in the system prompt:
|
||||
|
||||
### Example 1: Parsing Network Notation
|
||||
|
||||
```
|
||||
User: "Add network 192.168.1.0/24 to OSPF area 0"
|
||||
AI Output: {"networks": [{"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0}]}
|
||||
|
||||
User: "Add network 10.0.0.0/8 to OSPF area 1"
|
||||
AI Output: {"networks": [{"address": "10.0.0.0", "wildcard": "0.255.255.255", "area": 1}]}
|
||||
|
||||
Conversion: CIDR to Wildcard
|
||||
- /24 → 0.0.0.255
|
||||
- /16 → 0.0.255.255
|
||||
- /8 → 0.255.255.255
|
||||
- /30 → 0.0.0.3
|
||||
- /32 → 0.0.0.0
|
||||
```
|
||||
|
||||
### Example 2: Interface Naming Conventions
|
||||
|
||||
```
|
||||
User: "Configure interface gig0/0"
|
||||
AI Output: {"name": "GigabitEthernet0/0"}
|
||||
|
||||
User: "Configure fa0/1"
|
||||
AI Output: {"name": "FastEthernet0/1"}
|
||||
|
||||
User: "Configure eth0"
|
||||
AI Output: {"name": "Ethernet0/0"}
|
||||
|
||||
Abbreviation Mapping:
|
||||
- gi/gig/gigabit → GigabitEthernet
|
||||
- fa/fast → FastEthernet
|
||||
- eth → Ethernet
|
||||
- serial → Serial
|
||||
- lo/loopback → Loopback
|
||||
```
|
||||
|
||||
### Example 3: IP Address with Subnet
|
||||
|
||||
```
|
||||
User: "IP 192.168.1.1/24"
|
||||
AI Output: {"ip_address": "192.168.1.1", "subnet_mask": "255.255.255.0"}
|
||||
|
||||
User: "IP 10.0.0.1 255.255.255.252"
|
||||
AI Output: {"ip_address": "10.0.0.1", "subnet_mask": "255.255.255.252"}
|
||||
|
||||
User: "IP 2001:db8::1/64"
|
||||
AI Output: {"ipv6_address": "2001:db8::1/64"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### When Information is Missing
|
||||
|
||||
**User**: "Configure OSPF"
|
||||
|
||||
**AI Should Ask**:
|
||||
```
|
||||
"I need more information to configure OSPF. Please provide:
|
||||
- OSPF process ID
|
||||
- Router ID (optional)
|
||||
- Networks to advertise with their areas
|
||||
- Any passive interfaces (optional)"
|
||||
```
|
||||
|
||||
### When Request is Ambiguous
|
||||
|
||||
**User**: "Configure BGP with the ISP"
|
||||
|
||||
**AI Should Ask**:
|
||||
```
|
||||
"I need more details for BGP configuration:
|
||||
- Your local AS number
|
||||
- ISP's IP address and AS number
|
||||
- Networks to advertise (optional)
|
||||
- Any special settings like route-maps, passwords, etc. (optional)"
|
||||
```
|
||||
|
||||
### When Feature is Not Supported
|
||||
|
||||
**User**: "Configure MPLS LDP on this router"
|
||||
|
||||
**AI Response**:
|
||||
```
|
||||
"MPLS LDP configuration is not yet supported through the template system.
|
||||
Available features: OSPF, BGP, Interface, VLAN, ACL, NAT.
|
||||
Would you like me to help with any of these?"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices for AI Responses
|
||||
|
||||
1. **Always use tool calls** - Don't just output the data, call the render_device_config tool
|
||||
|
||||
2. **Validate before calling** - Check that all required fields are present
|
||||
|
||||
3. **Ask for clarification** - If critical information is missing, ask the user
|
||||
|
||||
4. **Be explicit about vendor/OS** - Don't assume, ask if not specified
|
||||
|
||||
5. **Handle edge cases**:
|
||||
- Multiple interfaces with similar configs
|
||||
- Conflicting parameters
|
||||
- Invalid values (out of range)
|
||||
|
||||
6. **Provide context** - Explain what you're about to configure before calling the tool
|
||||
|
||||
---
|
||||
|
||||
## Tool Definition for LangChain
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
from typing import Dict, Any
|
||||
|
||||
@tool
|
||||
def render_device_config(
|
||||
node_id: str,
|
||||
vendor: str,
|
||||
os_type: str,
|
||||
config_data: Dict[str, Any]
|
||||
) -> str:
|
||||
"""
|
||||
Render network device configuration using Jinja2 templates.
|
||||
|
||||
Instead of generating full configuration text, provide structured data
|
||||
that will be rendered through vendor-specific templates.
|
||||
|
||||
Args:
|
||||
node_id: GNS3 node identifier (e.g., "node-1", "R1")
|
||||
vendor: Device vendor - cisco, juniper, huawei, arista, mikrotik
|
||||
os_type: Operating system type - ios, iosxr, nx-os, junos, vrp, eos, routeros
|
||||
config_data: Structured configuration data (dict) for the features
|
||||
|
||||
Returns:
|
||||
Rendered configuration string or error message
|
||||
|
||||
Examples:
|
||||
>>> config = render_device_config(
|
||||
... node_id="R1",
|
||||
... vendor="cisco",
|
||||
... os_type="ios",
|
||||
... config_data={
|
||||
... "ospf": {
|
||||
... "enabled": True,
|
||||
... "process_id": 100,
|
||||
... "router_id": "1.1.1.1",
|
||||
... "networks": [
|
||||
... {"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0}
|
||||
... ]
|
||||
... }
|
||||
... }
|
||||
... )
|
||||
|
||||
Supported Features:
|
||||
- ospf: OSPF routing protocol
|
||||
- bgp: BGP routing protocol
|
||||
- interface: Interface configuration
|
||||
- vlan: VLAN configuration
|
||||
- acl: Access control lists
|
||||
- nat: NAT configuration
|
||||
- rip: RIP routing protocol
|
||||
- eigrp: EIGRP routing protocol
|
||||
"""
|
||||
from gns3server.agent.gns3_copilot.config_renderer import ConfigRenderer
|
||||
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
try:
|
||||
# Validate data
|
||||
feature = list(config_data.keys())[0] if len(config_data) == 1 else None
|
||||
if feature:
|
||||
renderer.validate_data(f"{vendor}_{feature}", config_data)
|
||||
|
||||
# Render
|
||||
if len(config_data) == 1:
|
||||
feature = list(config_data.keys())[0]
|
||||
config = renderer.render(vendor, os_type, feature, config_data)
|
||||
else:
|
||||
config = renderer.render_multi(vendor, os_type, config_data)
|
||||
|
||||
return f"Configuration rendered successfully:\n{config}"
|
||||
|
||||
except Exception as e:
|
||||
return f"Error: {str(e)}"
|
||||
|
||||
|
||||
@tool
|
||||
def list_available_templates() -> Dict[str, Any]:
|
||||
"""
|
||||
List all available configuration templates organized by vendor and OS type.
|
||||
|
||||
Returns:
|
||||
Dictionary of available templates:
|
||||
{
|
||||
"cisco": {
|
||||
"ios": ["ospf", "bgp", "interface", "vlan", "acl", "nat"],
|
||||
"nx-os": ["ospf", "bgp", "interface"]
|
||||
},
|
||||
"juniper": {
|
||||
"junos": ["ospf", "bgp", "interface"]
|
||||
}
|
||||
}
|
||||
|
||||
Use this to understand what features are supported for each vendor/OS combination.
|
||||
"""
|
||||
from gns3server.agent.gns3_copilot.config_renderer import ConfigRenderer
|
||||
|
||||
renderer = ConfigRenderer()
|
||||
return renderer.get_available_templates()
|
||||
|
||||
|
||||
@tool
|
||||
def validate_config_data(
|
||||
vendor: str,
|
||||
feature: str,
|
||||
config_data: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate configuration data against JSON schema before rendering.
|
||||
|
||||
Args:
|
||||
vendor: Device vendor (cisco, juniper, huawei, etc.)
|
||||
feature: Feature name (ospf, bgp, interface, etc.)
|
||||
config_data: Configuration data to validate
|
||||
|
||||
Returns:
|
||||
Validation result with status and optional error details
|
||||
|
||||
Example:
|
||||
>>> result = validate_config_data(
|
||||
... vendor="cisco",
|
||||
... feature="ospf",
|
||||
... config_data={"ospf": {"enabled": True, "process_id": 100}}
|
||||
... )
|
||||
>>> # Returns: {"status": "valid"}
|
||||
"""
|
||||
from gns3server.agent.gns3_copilot.config_renderer import ConfigRenderer
|
||||
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
try:
|
||||
renderer.validate_data(f"{vendor}_{feature}", config_data)
|
||||
return {"status": "valid", "message": "Configuration data is valid"}
|
||||
except Exception as e:
|
||||
return {"status": "invalid", "errors": str(e)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Agent Integration Example
|
||||
|
||||
```python
|
||||
# gns3server/agent/gns3_copilot/agent/config_agent.py
|
||||
|
||||
from langchain.agents import create_openai_functions_agent, AgentExecutor
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
|
||||
# Define tools
|
||||
tools = [
|
||||
render_device_config,
|
||||
list_available_templates,
|
||||
validate_config_data,
|
||||
# ... other GNS3 tools
|
||||
]
|
||||
|
||||
# Create prompt
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
("system", CONFIG_GENERATION_SYSTEM_PROMPT),
|
||||
MessagesPlaceholder(variable_name="chat_history", optional=True),
|
||||
("human", "{input}"),
|
||||
MessagesPlaceholder(variable_name="agent_scratchpad"),
|
||||
])
|
||||
|
||||
# Create agent
|
||||
agent = create_openai_functions_agent(llm, tools, prompt)
|
||||
agent_executor = AgentExecutor(
|
||||
agent=agent,
|
||||
tools=tools,
|
||||
verbose=True,
|
||||
handle_parsing_errors=True,
|
||||
max_iterations=5
|
||||
)
|
||||
|
||||
# Example usage
|
||||
async def configure_device(user_message: str):
|
||||
response = await agent_executor.ainvoke({
|
||||
"input": user_message,
|
||||
"chat_history": []
|
||||
})
|
||||
return response
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing the AI Prompts
|
||||
|
||||
Use these test cases to verify the AI generates correct structured data:
|
||||
|
||||
```python
|
||||
test_cases = [
|
||||
{
|
||||
"input": "Configure OSPF process 100 with network 192.168.1.0/24 in area 0",
|
||||
"expected_keys": ["ospf"],
|
||||
"expected_values": {
|
||||
"ospf.process_id": 100,
|
||||
"ospf.networks[0].address": "192.168.1.0",
|
||||
"ospf.networks[0].area": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"input": "Set up BGP AS 65001, neighbor 10.0.0.2 remote-as 65002",
|
||||
"expected_keys": ["bgp"],
|
||||
"expected_values": {
|
||||
"bgp.as_number": 65001,
|
||||
"bgp.neighbors[0].ip": "10.0.0.2",
|
||||
"bgp.neighbors[0].remote_as": 65002
|
||||
}
|
||||
},
|
||||
# ... more test cases
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Continuous Improvement
|
||||
|
||||
1. **Collect user interactions** - Save actual requests and AI responses
|
||||
2. **Analyze errors** - Find patterns in failed generations
|
||||
3. **Update prompts** - Refine examples and instructions
|
||||
4. **Expand schemas** - Add new features as needed
|
||||
5. **Vendor feedback** - Learn from network engineers
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
### What AI Should Do
|
||||
|
||||
| User Says | AI Generates |
|
||||
|-----------|-------------|
|
||||
| "OSPF process 100" | `{"ospf": {"enabled": True, "process_id": 100}}` |
|
||||
| "network 192.168.1.0/24 area 0" | `{"networks": [{"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0}]}` |
|
||||
| "BGP AS 65001" | `{"bgp": {"enabled": True, "as_number": 65001}}` |
|
||||
| "interface 192.168.1.1/24" | `{"ip_address": "192.168.1.1", "subnet_mask": "255.255.255.0"}` |
|
||||
|
||||
### What AI Should NOT Do
|
||||
|
||||
| Don't ❌ | Instead ✅ |
|
||||
|---------|-----------|
|
||||
| Output "router ospf 100" | Output `{"ospf": {"process_id": 100}}` |
|
||||
| Guess missing values | Ask user for missing values |
|
||||
| Assume vendor/OS | Ask or detect from node |
|
||||
| Mix features in one dict | Separate by feature key |
|
||||
| Use string for numbers | Use int: `process_id: 100` not `"process_id": "100"` |
|
||||
709
docs/gns3-copilot/todo/config-templates-implementation-guide.md
Normal file
709
docs/gns3-copilot/todo/config-templates-implementation-guide.md
Normal file
@ -0,0 +1,709 @@
|
||||
# Configuration Templates Implementation Guide
|
||||
|
||||
## Quick Start Examples
|
||||
|
||||
### Example 1: Configure OSPF on a Cisco Router
|
||||
|
||||
**User Request**:
|
||||
```
|
||||
"Configure OSPF on R1 with process ID 100, router-id 1.1.1.1.
|
||||
Include network 192.168.1.0/24 in area 0 and 10.0.0.0/8 in area 1.
|
||||
Make GigabitEthernet0/0 a passive interface."
|
||||
```
|
||||
|
||||
**AI Should Generate** (structured JSON):
|
||||
```json
|
||||
{
|
||||
"ospf": {
|
||||
"enabled": true,
|
||||
"process_id": 100,
|
||||
"router_id": "1.1.1.1",
|
||||
"networks": [
|
||||
{
|
||||
"address": "192.168.1.0",
|
||||
"wildcard": "0.0.0.255",
|
||||
"area": 0
|
||||
},
|
||||
{
|
||||
"address": "10.0.0.0",
|
||||
"wildcard": "0.255.255.255",
|
||||
"area": 1
|
||||
}
|
||||
],
|
||||
"passive_interfaces": ["GigabitEthernet0/0"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Agent Action**:
|
||||
```python
|
||||
# The agent calls the render tool with the structured data
|
||||
result = render_device_config(
|
||||
node_id="node-1",
|
||||
vendor="cisco",
|
||||
os_type="ios",
|
||||
config_data=ai_output
|
||||
)
|
||||
```
|
||||
|
||||
**Rendered Configuration**:
|
||||
```cisco
|
||||
router ospf 100
|
||||
router-id 1.1.1.1
|
||||
network 192.168.1.0 mask 0.0.0.255 area 0
|
||||
network 10.0.0.0 mask 0.255.255.255 area 1
|
||||
passive-interface GigabitEthernet0/0
|
||||
!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 2: Configure BGP with Multiple Neighbors
|
||||
|
||||
**User Request**:
|
||||
```
|
||||
"Configure BGP on R1 with AS 65001. Set up IBGP with R2 (10.0.0.2, AS 65001)
|
||||
and EBGP with ISP (203.0.13.2, AS 65002). Advertise network 192.168.0.0/16.
|
||||
Enable route-map INBOUND-FILTER on R2 inbound."
|
||||
```
|
||||
|
||||
**AI Generates**:
|
||||
```json
|
||||
{
|
||||
"bgp": {
|
||||
"enabled": true,
|
||||
"as_number": 65001,
|
||||
"router_id": "1.1.1.1",
|
||||
"log_neighbor_changes": true,
|
||||
"neighbors": [
|
||||
{
|
||||
"ip": "10.0.0.2",
|
||||
"remote_as": 65001,
|
||||
"description": "IBGP_Peer_R2",
|
||||
"next_hop_self": true,
|
||||
"route_map_in": "INBOUND-FILTER"
|
||||
},
|
||||
{
|
||||
"ip": "203.0.13.2",
|
||||
"remote_as": 65002,
|
||||
"description": "ISP_EBGP",
|
||||
"ebgp_multihop": 2
|
||||
}
|
||||
],
|
||||
"address_families": [
|
||||
{
|
||||
"type": "ipv4",
|
||||
"networks": [
|
||||
{
|
||||
"address": "192.168.0.0",
|
||||
"mask": "255.255.0.0"
|
||||
}
|
||||
],
|
||||
"neighbors": [
|
||||
{"ip": "10.0.0.2", "activate": true},
|
||||
{"ip": "203.0.13.2", "activate": true}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rendered Configuration**:
|
||||
```cisco
|
||||
router bgp 65001
|
||||
bgp router-id 1.1.1.1
|
||||
bgp log-neighbor-changes
|
||||
neighbor 10.0.0.2 remote-as 65001
|
||||
neighbor 10.0.0.2 description IBGP_Peer_R2
|
||||
neighbor 10.0.0.2 next-hop-self
|
||||
neighbor 10.0.0.2 route-map INBOUND-FILTER in
|
||||
neighbor 203.0.13.2 remote-as 65002
|
||||
neighbor 203.0.13.2 description ISP_EBGP
|
||||
neighbor 203.0.13.2 ebgp-multihop 2
|
||||
address-family ipv4
|
||||
network 192.168.0.0 mask 255.255.0.0
|
||||
neighbor 10.0.0.2 activate
|
||||
neighbor 203.0.13.2 activate
|
||||
exit-address-family
|
||||
!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 3: Configure Interfaces with IP Addresses
|
||||
|
||||
**User Request**:
|
||||
```
|
||||
"Configure GigabitEthernet0/0 with IP 192.168.1.1/24, description 'LAN Network'.
|
||||
Configure GigabitEthernet0/1 with IP 10.0.0.1/30, description 'WAN Link'.
|
||||
Both interfaces should be enabled."
|
||||
```
|
||||
|
||||
**AI Generates**:
|
||||
```json
|
||||
{
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "GigabitEthernet0/0",
|
||||
"description": "LAN Network",
|
||||
"ip_address": "192.168.1.1",
|
||||
"subnet_mask": "255.255.255.0",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "GigabitEthernet0/1",
|
||||
"description": "WAN Link",
|
||||
"ip_address": "10.0.0.1",
|
||||
"subnet_mask": "255.255.255.252",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Rendered Configuration**:
|
||||
```cisco
|
||||
interface GigabitEthernet0/0
|
||||
description LAN Network
|
||||
ip address 192.168.1.1 255.255.255.0
|
||||
no shutdown
|
||||
!
|
||||
interface GigabitEthernet0/1
|
||||
description WAN Link
|
||||
ip address 10.0.0.1 255.255.255.252
|
||||
no shutdown
|
||||
!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Example 4: Multi-Feature Configuration
|
||||
|
||||
**User Request**:
|
||||
```
|
||||
"Configure R1 as follows:
|
||||
- Hostname: CORE-R1
|
||||
- GigabitEthernet0/0: 192.168.1.1/24, LAN, enable NAT inside
|
||||
- GigabitEthernet0/1: 203.0.13.1/30, WAN, enable NAT outside
|
||||
- OSPF: process 100, router-id 1.1.1.1, advertise 192.168.1.0/24 in area 0
|
||||
- NAT: overload interface GigabitEthernet0/1 for 192.168.1.0/24"
|
||||
```
|
||||
|
||||
**AI Generates** (complete configuration):
|
||||
```json
|
||||
{
|
||||
"hostname": "CORE-R1",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "GigabitEthernet0/0",
|
||||
"description": "LAN",
|
||||
"ip_address": "192.168.1.1",
|
||||
"subnet_mask": "255.255.255.0",
|
||||
"nat_inside": true,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "GigabitEthernet0/1",
|
||||
"description": "WAN",
|
||||
"ip_address": "203.0.13.1",
|
||||
"subnet_mask": "255.255.255.252",
|
||||
"nat_outside": true,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"ospf": {
|
||||
"enabled": true,
|
||||
"process_id": 100,
|
||||
"router_id": "1.1.1.1",
|
||||
"networks": [
|
||||
{
|
||||
"address": "192.168.1.0",
|
||||
"wildcard": "0.0.0.255",
|
||||
"area": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"nat": {
|
||||
"inside_source": {
|
||||
"pool": "LAN_POOL",
|
||||
"network": "192.168.1.0",
|
||||
"mask": "255.255.255.0",
|
||||
"interface": "GigabitEthernet0/1",
|
||||
"overload": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 5: Juniper JunOS Configuration
|
||||
|
||||
**User Request**:
|
||||
```
|
||||
"Configure Juniper SRX with OSPF area 0 on interface ge-0/0/0.0 with IP 192.168.1.1/24.
|
||||
Set router-id to 10.1.1.1."
|
||||
```
|
||||
|
||||
**AI Generates**:
|
||||
```json
|
||||
{
|
||||
"ospf": {
|
||||
"enabled": true,
|
||||
"router_id": "10.1.1.1",
|
||||
"areas": [
|
||||
{
|
||||
"area_id": "0.0.0.0",
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "ge-0/0/0.0",
|
||||
"address": "192.168.1.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Template**: `config_templates/juniper/junos/ospf.j2`
|
||||
```jinja2
|
||||
{% if ospf.enabled %}
|
||||
protocols {
|
||||
ospf {
|
||||
{% if ospf.router_id %}
|
||||
router-id {{ ospf.router_id }};
|
||||
{% endif %}
|
||||
{% for area in ospf.areas %}
|
||||
area {{ area.area_id }} {
|
||||
{% for iface in area.interfaces %}
|
||||
interface {{ iface.name }} {
|
||||
{% if iface.address %}
|
||||
family inet {
|
||||
address {{ iface.address }};
|
||||
}
|
||||
{% endif %}
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
**Rendered Configuration**:
|
||||
```junos
|
||||
protocols {
|
||||
ospf {
|
||||
router-id 10.1.1.1;
|
||||
area 0.0.0.0 {
|
||||
interface ge-0/0/0.0 {
|
||||
family inet {
|
||||
address 192.168.1.1/24;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with LangGraph Agent
|
||||
|
||||
### Updated Agent Flow
|
||||
|
||||
```python
|
||||
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
|
||||
from langchain.agents import AgentExecutor, create_openai_functions_agent
|
||||
from langchain.tools import tool
|
||||
|
||||
@tool
|
||||
def render_and_apply_config(
|
||||
node_id: str,
|
||||
vendor: str,
|
||||
os_type: str,
|
||||
config_data: dict
|
||||
) -> str:
|
||||
"""Render configuration and apply to device"""
|
||||
from gns3server.agent.gns3_copilot.config_renderer import ConfigRenderer
|
||||
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
# Step 1: Validate
|
||||
try:
|
||||
renderer.validate_data(f"{vendor}_config", config_data)
|
||||
except Exception as e:
|
||||
return f"Validation failed: {e}"
|
||||
|
||||
# Step 2: Render
|
||||
try:
|
||||
if len(config_data) == 1:
|
||||
feature = list(config_data.keys())[0]
|
||||
config = renderer.render(vendor, os_type, feature, config_data)
|
||||
else:
|
||||
config = renderer.render_multi(vendor, os_type, config_data)
|
||||
|
||||
# Step 3: Apply to device (via telnet/console/SSH)
|
||||
# result = apply_config_to_node(node_id, config)
|
||||
return f"Configuration rendered successfully:\n{config}"
|
||||
|
||||
except Exception as e:
|
||||
return f"Rendering failed: {e}"
|
||||
|
||||
|
||||
# Updated agent prompt
|
||||
SYSTEM_PROMPT = """
|
||||
You are a network configuration assistant for GNS3.
|
||||
|
||||
When users ask to configure network devices:
|
||||
1. Extract the configuration requirements
|
||||
2. Generate STRUCTURED DATA (JSON/dict), NOT full configuration text
|
||||
3. Call the render_and_apply_config tool with the structured data
|
||||
4. The system will render the actual configuration using templates
|
||||
|
||||
Example for OSPF:
|
||||
- User: "Configure OSPF with process 100, network 192.168.1.0/24 in area 0"
|
||||
- You should output: {"ospf": {"enabled": true, "process_id": 100, ...}}
|
||||
|
||||
Available vendors: cisco, juniper, huawei, arista
|
||||
Available OS types: ios, iosxr, nexus, junos, vrp, eos
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Template Snippets Library
|
||||
|
||||
### OSPF Interface Templates
|
||||
|
||||
**Cisco IOS**:
|
||||
```jinja2
|
||||
{# ospf.j2 - Cisco IOS OSPF #}
|
||||
{% if ospf.enabled %}
|
||||
router ospf {{ ospf.process_id }}
|
||||
{% if ospf.router_id %}
|
||||
router-id {{ ospf.router_id }}
|
||||
{% endif %}
|
||||
{% for network in ospf.networks %}
|
||||
network {{ network.address }} mask {{ network.wildcard }} area {{ network.area }}
|
||||
{% endfor %}
|
||||
{% for iface in ospf.passive_interfaces %}
|
||||
passive-interface {{ iface }}
|
||||
{% endfor %}
|
||||
!
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
**Juniper JunOS**:
|
||||
```jinja2
|
||||
{# ospf.j2 - Juniper JunOS OSPF #}
|
||||
{% if ospf.enabled %}
|
||||
protocols {
|
||||
ospf {
|
||||
{% if ospf.router_id %}
|
||||
router-id {{ ospf.router_id }};
|
||||
{% endif %}
|
||||
{% for area in ospf.areas %}
|
||||
area {{ area.area_id }} {
|
||||
{% for iface in area.interfaces %}
|
||||
interface {{ iface.name }};
|
||||
{% endfor %}
|
||||
}
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
**Huawei VRP**:
|
||||
```jinja2
|
||||
{# ospf.j2 - Huawei VRP OSPF #}
|
||||
{% if ospf.enabled %}
|
||||
ospf {{ ospf.process_id }}
|
||||
{% if ospf.router_id %}
|
||||
router-id {{ ospf.router_id }}
|
||||
{% endif %}
|
||||
{% for area in ospf.areas %}
|
||||
area {{ area.area_id }}
|
||||
{% for network in area.networks %}
|
||||
network {{ network.address }} {{ network.wildcard }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Framework
|
||||
|
||||
### Unit Test for Template Rendering
|
||||
|
||||
```python
|
||||
# tests/agent/test_config_renderer.py
|
||||
|
||||
import pytest
|
||||
from gns3server.agent.gns3_copilot.config_renderer import ConfigRenderer
|
||||
|
||||
def test_ospf_cisco_ios():
|
||||
"""Test OSPF configuration rendering for Cisco IOS"""
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
data = {
|
||||
"ospf": {
|
||||
"enabled": True,
|
||||
"process_id": 100,
|
||||
"router_id": "1.1.1.1",
|
||||
"networks": [
|
||||
{"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
config = renderer.render("cisco", "ios", "ospf", data)
|
||||
|
||||
assert "router ospf 100" in config
|
||||
assert "router-id 1.1.1.1" in config
|
||||
assert "network 192.168.1.0 mask 0.0.0.255 area 0" in config
|
||||
|
||||
def test_bgp_cisco_ios():
|
||||
"""Test BGP configuration rendering for Cisco IOS"""
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
data = {
|
||||
"bgp": {
|
||||
"enabled": True,
|
||||
"as_number": 65001,
|
||||
"neighbors": [
|
||||
{"ip": "10.0.0.2", "remote_as": 65002}
|
||||
],
|
||||
"address_families": [
|
||||
{
|
||||
"type": "ipv4",
|
||||
"neighbors": [
|
||||
{"ip": "10.0.0.2", "activate": True}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
config = renderer.render("cisco", "ios", "bgp", data)
|
||||
|
||||
assert "router bgp 65001" in config
|
||||
assert "neighbor 10.0.0.2 remote-as 65002" in config
|
||||
assert "address-family ipv4" in config
|
||||
assert "neighbor 10.0.0.2 activate" in config
|
||||
|
||||
def test_interface_cisco_ios():
|
||||
"""Test interface configuration rendering"""
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
data = {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "GigabitEthernet0/0",
|
||||
"description": "Test Interface",
|
||||
"ip_address": "192.168.1.1",
|
||||
"subnet_mask": "255.255.255.0",
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
config = renderer.render("cisco", "ios", "interface", data)
|
||||
|
||||
assert "interface GigabitEthernet0/0" in config
|
||||
assert "description Test Interface" in config
|
||||
assert "ip address 192.168.1.1 255.255.255.0" in config
|
||||
assert "no shutdown" in config
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Integration
|
||||
|
||||
### New Controller Endpoint
|
||||
|
||||
```python
|
||||
# gns3server/api/routes/controller/config_templates.py
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from typing import Dict, Any
|
||||
from gns3server.agent.gns3_copilot.config_renderer import ConfigRenderer
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/config-templates")
|
||||
async def list_templates() -> Dict[str, Any]:
|
||||
"""List all available configuration templates"""
|
||||
renderer = ConfigRenderer()
|
||||
return renderer.get_available_templates()
|
||||
|
||||
@router.post("/config-templates/render")
|
||||
async def render_config_template(
|
||||
vendor: str,
|
||||
os_type: str,
|
||||
feature: str,
|
||||
data: Dict[str, Any]
|
||||
) -> Dict[str, str]:
|
||||
"""Render a configuration template with provided data"""
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
try:
|
||||
config = renderer.render(vendor, os_type, feature, data)
|
||||
return {"status": "success", "config": config}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@router.post("/config-templates/validate")
|
||||
async def validate_config_data(
|
||||
schema_name: str,
|
||||
data: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""Validate configuration data against schema"""
|
||||
renderer = ConfigRenderer()
|
||||
|
||||
try:
|
||||
is_valid = renderer.validate_data(schema_name, data)
|
||||
return {"status": "valid"}
|
||||
except Exception as e:
|
||||
return {"status": "invalid", "errors": str(e)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt Engineering for AI
|
||||
|
||||
### System Prompt Template
|
||||
|
||||
```python
|
||||
CONFIG_GENERATION_PROMPT = """
|
||||
You are a network configuration expert. When users request device configurations:
|
||||
|
||||
1. UNDERSTAND the requirements (vendor, OS, features, parameters)
|
||||
2. GENERATE structured data (dict/JSON), NOT full configuration text
|
||||
3. CALL the appropriate rendering tool with the structured data
|
||||
|
||||
RULES:
|
||||
- NEVER output full configuration text directly
|
||||
- ALWAYS use structured data format
|
||||
- Include only the parameters that are explicitly mentioned
|
||||
- Use correct data types (int for numbers, bool for flags)
|
||||
- Follow the JSON schema for each feature
|
||||
|
||||
VENDORS: cisco, juniper, huawei, arista, mikrotik
|
||||
OS TYPES: ios, iosxr, nx-os, junos, vrp, eos, routeros
|
||||
|
||||
FEATURES AVAILABLE:
|
||||
- ospf: process_id, router_id, networks[{address,wildcard,area}]
|
||||
- bgp: as_number, router_id, neighbors[{ip,remote_as,description,...}]
|
||||
- interface: name, ip_address, subnet_mask, description, enabled
|
||||
- vlan: id, name, interfaces[]
|
||||
- acl: number, rules[{action,protocol,source,destination}]
|
||||
- nat: inside_source, outside_source, static
|
||||
|
||||
EXAMPLE:
|
||||
|
||||
User: "Configure OSPF process 100 with router-id 1.1.1.1, network 192.168.1.0/24 area 0"
|
||||
|
||||
Your tool call:
|
||||
render_device_config(
|
||||
node_id="node-1",
|
||||
vendor="cisco",
|
||||
os_type="ios",
|
||||
config_data={{
|
||||
"ospf": {{
|
||||
"enabled": True,
|
||||
"process_id": 100,
|
||||
"router_id": "1.1.1.1",
|
||||
"networks": [
|
||||
{{"address": "192.168.1.0", "wildcard": "0.0.0.255", "area": 0}}
|
||||
]
|
||||
}}
|
||||
}}
|
||||
)
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Phase 1: Core Templates (Week 1-2)
|
||||
- Cisco IOS: ospf, bgp, interface, vlan, acl
|
||||
- Juniper JunOS: ospf, bgp, interface
|
||||
- Schema definitions
|
||||
|
||||
### Phase 2: Extended Features (Week 3-4)
|
||||
- NAT, QoS, Multicast
|
||||
- Nexus, IOS-XR variants
|
||||
- Huawei VRP support
|
||||
|
||||
### Phase 3: Advanced Features (Week 5-6)
|
||||
- MPLS, VPN
|
||||
- Firewall policies (ASA, SRX)
|
||||
- Automation and testing
|
||||
|
||||
### Phase 4: Integration (Week 7-8)
|
||||
- Integrate with AI Copilot
|
||||
- Add rendering endpoint to API
|
||||
- Testing and validation
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Template Design**:
|
||||
- Keep templates simple and focused
|
||||
- Use conditionals sparingly
|
||||
- Add comments for complex logic
|
||||
- Follow vendor syntax conventions
|
||||
|
||||
2. **Schema Design**:
|
||||
- Define all fields with types
|
||||
- Add descriptions for AI
|
||||
- Include validation rules
|
||||
- Use enums for fixed values
|
||||
|
||||
3. **AI Prompting**:
|
||||
- Provide clear examples
|
||||
- Specify expected output format
|
||||
- Include error handling guidance
|
||||
- Test with various inputs
|
||||
|
||||
4. **Testing**:
|
||||
- Unit test each template
|
||||
- Test with real devices
|
||||
- Validate schemas
|
||||
- Integration testing
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue**: Template not found
|
||||
```
|
||||
Solution: Check template path format: "{vendor}/{os_type}/{feature}.j2"
|
||||
```
|
||||
|
||||
**Issue**: Invalid data structure
|
||||
```
|
||||
Solution: Validate against JSON schema first
|
||||
```
|
||||
|
||||
**Issue**: Rendering produces empty config
|
||||
```
|
||||
Solution: Check if feature flag "enabled" is set to True
|
||||
```
|
||||
|
||||
**Issue**: Syntax error in rendered config
|
||||
```
|
||||
Solution: Review template logic, check conditional statements
|
||||
```
|
||||
1084
docs/gns3-copilot/todo/jinja2-config-templates-system.md
Normal file
1084
docs/gns3-copilot/todo/jinja2-config-templates-system.md
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user