feat: add fault injection system and external skills repository

## Summary

Add a complete fault injection system for GNS3 Copilot, migrate all
skills from local Python files to an external Git repository with
hot reload support, and restructure Copilot API under /copilot/.

## Key Changes

### Fault Injection
- New troubleshooting_injection mode with InjectionSkillsTool
- 368 fault scenarios across 39 protocol categories
- Context-based filtering (LLM must pass topology protocols)

### External Skills Repository
- SkillsManager: Git clone/pull, version tracking, smart updates
- SkillsLoader: YAML skills + Markdown prompts from external repo
- Hot reload via POST /copilot/reload/skills
- Configurable via gns3_server.conf

### Architecture
- API unified under /copilot/ prefix
- SkillsManager moved from Controller to agent module
- Lazy initialization with startup background preload
- Per-command Git timeout, smart update checks
- Forbidden commands hot-reloadable from external repo
- 32 INFO logs downgraded to DEBUG
This commit is contained in:
YueGuobin committed 2026-05-11 01:24:55 +08:00
1 parent 534bb5eded
commit e68cc17ed5
42 files changed
+2182 -1260

No files matched your search

+27 -17
View File
@@ -414,20 +414,30 @@ function handleToolCallEvent(chunk) {
## API Endpoints
All endpoints are under `/v3/projects/{project_id}/chat/` path.
All Copilot endpoints are under `/v3/copilot/` path.
### Chat Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/stream` | Streaming Chat (main interface) |
| GET | `/sessions` | List sessions (sorted by pin and update time) |
| GET | `/sessions/{session_id}/history` | Get session history |
| PATCH | `/sessions/{session_id}` | Rename session |
| DELETE | `/sessions/{session_id}` | Delete session |
| POST | `/sessions/{session_id}/abort` | Abort ongoing streaming session |
| PUT | `/sessions/{session_id}/pin` | Pin session |
| DELETE | `/sessions/{session_id}/pin` | Unpin session |
| POST | `/copilot/projects/{project_id}/chat/stream` | Streaming Chat (main interface) |
| GET | `/copilot/projects/{project_id}/chat/sessions` | List sessions (sorted by pin and update time) |
| GET | `/copilot/projects/{project_id}/chat/sessions/{session_id}/history` | Get session history |
| PATCH | `/copilot/projects/{project_id}/chat/sessions/{session_id}` | Rename session |
| DELETE | `/copilot/projects/{project_id}/chat/sessions/{session_id}` | Delete session |
| POST | `/copilot/projects/{project_id}/chat/sessions/{session_id}/abort` | Abort ongoing streaming session |
| PUT | `/copilot/projects/{project_id}/chat/sessions/{session_id}/pin` | Pin session |
| DELETE | `/copilot/projects/{project_id}/chat/sessions/{session_id}/pin` | Unpin session |
### POST /v3/projects/{project_id}/chat/stream
### Skills Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/copilot/reload/skills` | Hot reload skills, prompts, and forbidden commands |
See [Skills Repository](skills-repository.md) for details on the reload process and configuration.
### POST /v3/copilot/projects/{project_id}/chat/stream
**Function**: Streaming conversation interface
@@ -478,7 +488,7 @@ data: {"type": "content", "content": "! I can help", "session_id": "d7e76375-696
data: {"type": "done", "session_id": "d7e76375-6960-419a-9367-211ef64af877"}
```
### GET /v3/projects/{project_id}/chat/sessions
### GET /v3/copilot/projects/{project_id}/chat/sessions
**Function**: List all sessions in a project
@@ -514,7 +524,7 @@ data: {"type": "done", "session_id": "d7e76375-6960-419a-9367-211ef64af877"}
]
```
### GET /v3/projects/{project_id}/chat/sessions/{session_id}/history
### GET /v3/copilot/projects/{project_id}/chat/sessions/{session_id}/history
**Function**: Get complete history of a session
@@ -563,7 +573,7 @@ data: {"type": "done", "session_id": "d7e76375-6960-419a-9367-211ef64af877"}
}
```
### PATCH /v3/projects/{project_id}/chat/sessions/{session_id}
### PATCH /v3/copilot/projects/{project_id}/chat/sessions/{session_id}
**Function**: Rename session
@@ -581,13 +591,13 @@ data: {"type": "done", "session_id": "d7e76375-6960-419a-9367-211ef64af877"}
**Response**: Updated session information
### DELETE /v3/projects/{project_id}/chat/sessions/{session_id}
### DELETE /v3/copilot/projects/{project_id}/chat/sessions/{session_id}
**Function**: Delete session and all its checkpoint data
**Response**: 204 No Content
### POST /v3/projects/{project_id}/chat/sessions/{session_id}/abort
### POST /v3/copilot/projects/{project_id}/chat/sessions/{session_id}/abort
**Function**: Abort an ongoing streaming session
@@ -615,7 +625,7 @@ Graph conditional edge checks flag
Stream ends → yields aborted tool_end events → done
```
### PUT /v3/projects/{project_id}/chat/sessions/{session_id}/pin
### PUT /v3/copilot/projects/{project_id}/chat/sessions/{session_id}/pin
**Function**: Pin session to top of list
@@ -630,7 +640,7 @@ Stream ends → yields aborted tool_end events → done
}
```
### DELETE /v3/projects/{project_id}/chat/sessions/{session_id}/pin
### DELETE /v3/copilot/projects/{project_id}/chat/sessions/{session_id}/pin
**Function**: Unpin session
@@ -86,14 +86,14 @@ flowchart TD
### Forbidden Commands Configuration
**File:** `gns3server/agent/gns3_copilot/config/forbidden_commands.txt`
The forbidden commands list is loaded from the external [GNS3-Skills](https://github.com/yueguobin/GNS3-Skills) repository at `config/forbidden_commands.txt`.
**Format:**
- One command pattern per line
- **Prefix matching** (case-insensitive) — matches the beginning of each command
- Empty lines and lines starting with `#` are ignored
**Default patterns (used when config file is missing):**
**Default patterns (used when skills repository is unavailable):**
| Pattern | Reason |
|---------|--------|
@@ -140,7 +140,7 @@ flowchart TD
| `filter_forbidden_commands(commands)` | Returns `(allowed_commands, blocked_commands_info)` |
| `is_command_forbidden(command)` | Check if a single command matches a forbidden pattern |
| `get_forbidden_commands()` | Get current forbidden patterns list |
| `reload_forbidden_commands()` | Clear cache, reload from file on next access |
| `reload_forbidden_commands()` | Directly load and cache commands from skills repository |
**Integration points:**
- `display_tools_nornir.py` — `_filter_forbidden_commands_from_device_configs()`
@@ -168,7 +168,7 @@ Applies to any command with embedded newlines: `banner`, multi-line ACLs, route-
### Customizing Forbidden Commands
Edit `gns3server/agent/gns3_copilot/config/forbidden_commands.txt` and either restart the server or call `reload_forbidden_commands()` to apply changes without restart.
Edit `config/forbidden_commands.txt` in the [GNS3-Skills repository](https://github.com/yueguobin/GNS3-Skills) and push the changes, then call `POST /copilot/reload/skills` to apply them without restarting the server.
## Implementation Verification
@@ -214,11 +214,11 @@ Edit `gns3server/agent/gns3_copilot/config/forbidden_commands.txt` and either re
|---------|----------|
| Command blocked unexpectedly | Check `blocked_commands` in result, identify matching pattern, edit `forbidden_commands.txt` |
| "File not found, using defaults" | Verify `config/forbidden_commands.txt` exists and is readable |
| Changes not taking effect | Restart server or call `reload_forbidden_commands()` |
| Changes not taking effect | Call `POST /copilot/reload/skills` or restart server |
## Related Documentation
- [Tool Implementation](../gns3-copilot/tools_v2/README.md)
- [GNS3-Copilot Documentation](../README.md)
- [Contributing Guide](../../CONTRIBUTING.md)
- [Skills Repository](skills-repository.md)
- [Fault Injection](fault-injection.md)
- [Chat API](chat-api.md)
@@ -0,0 +1,159 @@
<!--
SPDX-License-Identifier: CC-BY-SA-4.0
See LICENSE file for licensing information.
-->
> This documentation is organized by AI with reference to actual code. AI can make mistakes — please verify against the source code when in doubt.
# Fault Injection Feature
## Overview
The fault injection feature enables GNS3 Copilot to automatically inject realistic network faults into GNS3 labs for troubleshooting training. The agent analyzes the topology, selects an appropriate fault based on the configured protocols, injects it, and documents the results.
## Architecture
```mermaid
graph TD
subgraph "Agent Workflow"
TG[Topology Gathering]
CFG[Config Collection]
FA[Fault Analysis]
FI[Fault Injection]
FD[Fault Documentation]
end
subgraph "Skills Repository"
IS[INJECTION_SKILLS_REGISTRY<br/>39 categories, 368 issues]
SK[injection_skills Tool]
end
subgraph "Tools"
DC[execute_multiple_device_commands]
CC[execute_multiple_device_config_commands]
GK[GNS3TopologyTool]
end
TG -->|Get topology| GK
CFG -->|Get configs| DC
FA -->|Query filtered skills| IS
FA -->|Select fault| SK
FI -->|Inject config changes| CC
FD -->|Output report| FD
```
## Injection Skills Tool
The `InjectionSkillsTool` (LangChain `BaseTool`) is the primary interface for querying available faults.
### Listing Faults (with context filter)
The LLM MUST pass topology context when listing faults:
```json
{"action": "list", "context": ["ospf", "bgp", "vlan"]}
```
This returns only faults matching the protocols found in the topology. The tool rejects calls without `context`:
```json
{
"error": "context parameter is required when action='list'",
"hint": "Analyze the topology and device configurations first...",
"available_categories": ["bgp", "interface", "mpls", "ospf", ...]
}
```
### Getting Fault Details
Token-efficient usage pattern:
```json
// Step 1: List issue names only (~300 tokens)
{"device_type": "injection_ospf", "detail": "index"}
// Step 2: Get single issue detail (~500 tokens)
{"device_type": "injection_ospf", "issue": "ospf_hello_dead_mismatch"}
```
### Parameters
| Parameter | Required | Description |
|-----------|----------|-------------|
| `action` | No (`"get"`) | `"list"` to browse, `"get"` for details |
| `context` | **Yes** for `action="list"` | Protocols found in topology: `["ospf", "bgp"]` |
| `device_type` | Yes for `action="get"` | e.g. `"injection_ospf"` |
| `detail` | No (`"full"`) | `"index"` (names), `"summary"` (+desc), `"full"` (all) |
| `issue` | No | Single issue key for targeted detail |
## Fault Injection Workflow
```mermaid
sequenceDiagram
participant LLM
participant InjectionSkillsTool
participant TopologyTool
participant DeviceCommands
LLM->>TopologyTool: Get topology info
TopologyTool-->>LLM: Topology + device types
LLM->>DeviceCommands: Get device configs
DeviceCommands-->>LLM: Running configurations
Note over LLM: Analyze which protocols are in use
LLM->>InjectionSkillsTool: {"action": "list", "context": ["ospf", "vlan"]}
InjectionSkillsTool-->>LLM: Matching fault categories
LLM->>InjectionSkillsTool: {"device_type": "injection_ospf", "detail": "index"}
InjectionSkillsTool-->>LLM: Issue names (token-efficient)
LLM->>InjectionSkillsTool: {"device_type": "injection_ospf", "issue": "ospf_hello_dead_mismatch"}
InjectionSkillsTool-->>LLM: Full fault detail + config commands
LLM->>DeviceCommands: Inject fault configuration
DeviceCommands-->>LLM: Execution result
Note over LLM: Document fault in response
```
## Injection Skills Repository
Skills are organized by protocol/category in the external [GNS3-Skills](https://github.com/yueguobin/GNS3-Skills) repository:
| Category | File | Example Issues |
|----------|------|----------------|
| OSPF | `injection/ospf_issues.yaml` | Hello/Dead mismatch, MTU mismatch, area mismatch |
| BGP | `injection/bgp_issues.yaml` | AS-path prepend, next-hop unreachable, route filtering |
| VLAN | `injection/vlan_issues.yaml` | Trunk allowed mismatch, native VLAN mismatch |
| STP | `injection/stp_issues.yaml` | Root guard, loop guard, port priority |
| MPLS | `injection/mpls_issues.yaml` | LDP session down, label binding failure |
| ... | 34 more files | 368 issues total |
## Recovery
Each injected fault includes restore commands in the documentation. The LLM always provides commands to fully revert all changes.
## API Endpoint
### POST /copilot/projects/{project_id}/chat/inject
Dedicated endpoint for fault injection. Internally sets `copilot_mode` to `troubleshooting_injection` and runs the agent.
**Request:**
```json
{
"message": "Inject a network fault for troubleshooting practice",
"session_id": "optional-session-uuid"
}
```
**Response:** Server-Sent Events (SSE) stream identical to the chat stream endpoint.
## Related Documentation
- [Skills Repository](skills-repository.md)
- [Command Security](command-security.md)
- [Chat API](chat-api.md)
@@ -0,0 +1,171 @@
<!--
SPDX-License-Identifier: CC-BY-SA-4.0
See LICENSE file for licensing information.
-->
> This documentation is organized by AI with reference to actual code. AI can make mistakes — please verify against the source code when in doubt.
# External Skills Repository
## Overview
GNS3 Copilot loads all skills, prompts, and security configurations from an external Git repository at [github.com/yueguobin/GNS3-Skills](https://github.com/yueguobin/GNS3-Skills). This enables dynamic updates without server redeployment.
The repository provides:
- **Injection skills** (39 categories): Network fault scenarios for troubleshooting practice
- **Device skills**: Device-specific command knowledge (VPCS, etc.)
- **Feature skills**: Topology planning, network design
- **System prompts**: Agent personality and behavior definitions
- **Forbidden commands**: Security rules for command filtering
## Architecture
```mermaid
graph TD
subgraph "GNS3-Skills Repository"
YAML[injection/*.yaml<br/>device/*.yaml<br/>feature/*.yaml]
MD[prompts/*.md]
CFG[config/forbidden_commands.txt]
end
subgraph "GNS3 Server"
SM[SkillsManager]
SL[SkillsLoader]
REG[Registry<br/>SKILLS_REGISTRY<br/>INJECTION_SKILLS_REGISTRY]
PROMPT[PROMPTS_CACHE]
FC[command_filter]
end
YAML --> SL
MD --> SL
CFG --> FC
SL --> REG
SL --> PROMPT
SM --> SL
SM -->|git pull| YAML
```
## Repository Structure
```
GNS3-Skills/
├── injection/ # 39 YAML files, one per protocol/category
│ ├── ospf_issues.yaml
│ ├── bgp_issues.yaml
│ ├── vlan_issues.yaml
│ └── ...
├── device/ # Device-specific skills
│ └── vpcs.yaml
├── feature/ # Feature skills
│ └── topology_planner.yaml
├── prompts/ # System prompts (Markdown)
│ ├── teaching_assistant.md
│ ├── lab_automation_assistant.md
│ ├── troubleshooting_injection.md
│ └── title.md
└── config/ # Security configuration
└── forbidden_commands.txt
```
## Configuration
Skills repository settings are configured in `gns3_server.conf` under the `[Server]` section:
```ini
[Server]
skills_repo_url = https://github.com/yueguobin/GNS3-Skills.git
skills_repo_branch = main
skills_auto_update = true
```
| Setting | Default | Description |
|---------|---------|-------------|
| `skills_repo_url` | `https://github.com/yueguobin/GNS3-Skills.git` | Git repository URL |
| `skills_repo_branch` | `main` | Git branch to track |
| `skills_auto_update` | `true` | Automatically pull on reload |
## Initialization Flow
```mermaid
sequenceDiagram
participant Server
participant agent/__init__.py
participant SkillsManager
participant Git
Server->>agent/__init__.py: Import module
agent/__init__.py->>SkillsManager: Start background init thread
Note over SkillsManager: Thread.join(5s timeout)
SkillsManager->>Git: Check local repo exists?
alt No local repo
Git->>SkillsManager: git clone (with timeout env)
else Local repo exists
SkillsManager->>Git: Check uncommitted changes?
alt Has uncommitted changes
Git-->>SkillsManager: Warn, skip pull
else No changes
SkillsManager->>Git: git fetch (timeout 10s)
SkillsManager->>Git: Behind remote?
alt Behind
Git->>SkillsManager: git pull
else Up to date
Git-->>SkillsManager: Nothing to do
end
end
end
SkillsManager->>SkillsManager: reload_skills() - load YAML files
SkillsManager->>SkillsManager: reload_prompts() - load Markdown files
```
### Git Timeout Configuration
Git operations use per-command environment variables to prevent hanging:
```python
_GIT_TIMEOUT_ENV = {
'GIT_HTTP_TIMEOUT': '10', # Connection timeout (default: 120s)
'GIT_HTTP_LOW_SPEED_TIME': '5', # Slow speed threshold window
'GIT_HTTP_LOW_SPEED_LIMIT': '1000', # < 1 KB/s = slow → abort
}
```
These apply only to the specific `clone`/`fetch`/`pull` subprocess, not to the global environment.
## API Endpoint
### POST /copilot/reload/skills
Triggers a full reload of the skills repository. Performs one git update check, then reloads all skills, prompts, and forbidden commands from local files.
**Response:**
```json
{
"success": true,
"skills": true,
"skill_count": 39,
"prompts": true,
"prompt_count": 4,
"forbidden_commands": 6,
"version": "abc123def456..."
}
```
| Field | Description |
|-------|-------------|
| `success` | Overall success (true if skills or prompts loaded) |
| `skills` | Skills reload result |
| `skill_count` | Number of injection skills loaded |
| `prompts` | Prompts reload result |
| `prompt_count` | Number of prompts loaded |
| `forbidden_commands` | Number of forbidden command patterns |
| `version` | Git commit hash of the repository |
## Related Documentation
- [Fault Injection](fault-injection.md)
- [Command Security](command-security.md)
- [Chat API](chat-api.md)