overgrowth / agent /parsing.py
Graham Paasch
Import Overgrowth app
b3d02a4
Raw
History Blame
5.17 kB
import json
from typing import List, Dict
def to_pretty_json(data) -> str:
return json.dumps(data, indent=2, sort_keys=True)
# Known presets from the Track 1 MCP server:
PRESET_KEYWORDS = [
{
"change_type": "vlan",
"preset_id": "leaf_tor_vlan_stage",
"keywords": ["stage", "staging", "test"],
"default": False,
},
{
"change_type": "vlan",
"preset_id": "leaf_tor_vlan_commit",
"keywords": ["commit", "production", "prod", "rollout"],
"default": False,
},
{
"change_type": "interface",
"preset_id": "tor_uplink_enable",
"keywords": ["enable uplink", "bring up uplink", "turn on uplink"],
"default": False,
},
{
"change_type": "interface",
"preset_id": "tor_uplink_shutdown",
"keywords": ["shutdown uplink", "shut down uplink", "disable uplink"],
"default": False,
},
{
"change_type": "bgp_neighbor",
"preset_id": "leaf_bgp_fabric_neighbor_add",
"keywords": ["add bgp", "new neighbor", "add neighbor"],
"default": False,
},
{
"change_type": "bgp_neighbor",
"preset_id": "leaf_bgp_fabric_neighbor_remove",
"keywords": ["remove bgp", "delete neighbor", "remove neighbor"],
"default": False,
},
]
def _guess_preset(change_text: str) -> Dict[str, str]:
text = change_text.lower()
# First pass: explicit keywords.
for preset in PRESET_KEYWORDS:
for kw in preset["keywords"]:
if kw in text:
return {
"change_type": preset["change_type"],
"preset_id": preset["preset_id"],
"mode": "lightning",
}
# Second pass: vague cues (VLAN vs interface vs bgp)
if "vlan" in text:
return {
"change_type": "vlan",
"preset_id": "leaf_tor_vlan_stage",
"mode": "lightning",
}
if "uplink" in text or "interface" in text:
return {
"change_type": "interface",
"preset_id": "tor_uplink_enable",
"mode": "lightning",
}
if "bgp" in text or "neighbor" in text:
return {
"change_type": "bgp_neighbor",
"preset_id": "leaf_bgp_fabric_neighbor_add",
"mode": "lightning",
}
# Fallback: safest-ish default.
return {
"change_type": "vlan",
"preset_id": "leaf_tor_vlan_stage",
"mode": "lightning",
}
def _guess_device(change_text: str) -> str:
"""
Ultra-naive device extraction: look for leaf-XX / tor-XX / core-XX tokens,
otherwise default to leaf-01.
"""
text = change_text.lower()
for prefix in ["leaf-", "tor-", "core-"]:
idx = text.find(prefix)
if idx != -1:
# grab token like leaf-01
token = text[idx:].split()[0].strip(",.;:")
return token
return "leaf-01"
def parse_change_request(change_text: str) -> List[Dict]:
"""
For now, treat the entire request as one step.
Later you can split into multiple steps by sentence/semicolon/etc.
"""
base = _guess_preset(change_text)
device = _guess_device(change_text)
step_json = {
"device": device,
"action": f"{base['change_type']}_change",
"change_type": base["change_type"],
"preset_id": base["preset_id"],
"mode": base["mode"],
"raw_text": change_text.strip(),
}
step = {
"description": change_text.strip(),
"json": step_json,
}
return [step]
def build_diff_summaries(steps: List[Dict]) -> List[Dict]:
"""
Produces simple 'diff-ish' summaries per step.
"""
summaries = []
for step in steps:
j = step["json"]
device = j.get("device", "device")
change_type = j.get("change_type", "change")
preset_id = j.get("preset_id", "preset")
title = f"{change_type} change on {device} ({preset_id})"
body = (
f"# BEFORE (conceptual)\n"
f"# - configuration in stable state for {change_type} preset '{preset_id}'\n\n"
f"# AFTER (conceptual)\n"
f"# - configuration reflecting applied preset '{preset_id}' on {device}\n"
)
summaries.append({"title": title, "body": body})
return summaries
def build_rollback_plan(steps: List[Dict]) -> List[Dict]:
"""
Invert each step in reverse order.
"""
rollback_steps = []
for step in reversed(steps):
j = step["json"]
device = j.get("device", "device")
change_type = j.get("change_type", "change")
preset_id = j.get("preset_id", "preset")
description = f"Rollback {change_type} change on {device} (preset {preset_id})"
commands = [
f"# rollback placeholder for {change_type} change on {device}",
f"# original preset: {preset_id}",
]
rollback_steps.append(
{
"description": description,
"commands": commands,
}
)
return rollback_steps