feat: Jinja2 template support in device_config_send

- Add optional 'template' param with Jinja2 syntax
- Each device entry can use 'vars' dict instead of 'config_commands'
- Template rendered per device, merged with existing commands
- Rendering errors returned inline for AI self-correction
This commit is contained in:
YueGuobin 2026-06-14 00:27:13 +08:00
parent c647805cfb
commit 06e1511773
No known key found for this signature in database
2 changed files with 40 additions and 0 deletions

View File

@ -1296,9 +1296,15 @@ async def device_config_send(
device_configs: Annotated[list, Field(
description="List of device configs. Each entry: {\"device_name\": \"R1\", \"config_commands\": [\"int lo0\", \"ip add 1.1.1.1 255.255.255.255\"]}"
)],
template: Annotated[str | None, Field(description="Optional Jinja2 template. Use with vars in each device to reduce token usage for batch config. Example: \"interface lo{{ n }}\\nip address {{ ip }} 255.255.255.255\"")] = None,
) -> list[dict[str, Any]]:
"""Send configuration commands to network devices via console (telnet/SSH).
Two modes:
1. Direct commands: each device has config_commands=[...]
2. Jinja2 template: provide template + vars per device template is rendered for each
Example: device_configs=[{\"device_name\": \"R1\", \"vars\": {\"n\": 0, \"ip\": \"1.1.1.1\"}}]
Devices must be started first (use node_start or node_start_all).
Device type is auto-detected from the 'device_type:<type>' tag on each node.
Common device types: cisco_ios_telnet, cisco_xr_telnet, huawei_telnet, gns3_huawei_telnet_ce

View File

@ -33,18 +33,52 @@ import json
import logging
from typing import Any
from jinja2 import Template as JinjaTemplate, TemplateError as JinjaError
log = logging.getLogger(__name__)
def _render_template(template: str, device_configs: list[dict]) -> list[dict]:
"""Render a Jinja2 template for each device's vars into config_commands.
Each device in device_configs can have:
- "vars": dict of template variables (rendered into config_commands)
- "config_commands": merged after rendering if already present
"""
jinja = JinjaTemplate(template)
results = []
for dev in device_configs:
rendered = dev.copy()
vars_data = rendered.pop("vars", {})
if vars_data:
try:
output = jinja.render(**vars_data)
lines = [l for l in output.splitlines() if l.strip()]
existing = rendered.get("config_commands", [])
rendered["config_commands"] = existing + lines
except JinjaError as e:
error_msg = f"Template rendering failed for '{dev.get('device_name')}': {e}"
log.error(error_msg)
return [{"error": error_msg}]
results.append(rendered)
return results
# ── Tool handlers ──────────────────────────────────────────────────────────
def device_config_send_handler(params: dict[str, Any], gns3_ctx: dict[str, Any]) -> list[dict[str, Any]]:
"""Send configuration commands to network devices via console."""
project_id = params.get("project_id")
device_configs = params.get("device_configs")
template = params.get("template")
if not project_id or not device_configs:
return [{"error": "project_id and device_configs are required"}]
if template:
device_configs = _render_template(template, device_configs)
if len(device_configs) == 1 and "error" in device_configs[0]:
return device_configs
from gns3server.agent.gns3_copilot.tools_v2.config_tools_nornir import ExecuteMultipleDeviceConfigCommands
tool = ExecuteMultipleDeviceConfigCommands()