mirror of
https://github.com/Jerryplusy/AI-powered-switches.git
synced 2025-12-05 18:21:57 +00:00
Compare commits
No commits in common. "a57e4d4b46b886f949dd08e569efd457663215f1" and "90f127b5cec4f810b50f4c0669aedde2138859ab" have entirely different histories.
a57e4d4b46
...
90f127b5ce
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -3,5 +3,5 @@
|
|||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.13" />
|
<option name="sdkName" value="Python 3.13" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (AI-powered-switches)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
@ -126,40 +126,6 @@ async def apply_config(request: ConfigRequest):
|
|||||||
status_code=500,
|
status_code=500,
|
||||||
detail=f"Failed to apply config: {str(e)}"
|
detail=f"Failed to apply config: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CLICommandRequest(BaseModel):
|
|
||||||
switch_ip: str
|
|
||||||
commands: List[str] # 前端生成的CLI命令列表
|
|
||||||
is_ensp: bool = False # 是否为eNSP模拟器模式
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/execute_cli_commands", response_model=dict)
|
|
||||||
async def execute_cli_commands(request: CLICommandRequest):
|
|
||||||
"""
|
|
||||||
执行前端生成的CLI命令
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
configurator = SwitchConfigurator(
|
|
||||||
username=settings.SWITCH_USERNAME,
|
|
||||||
password=settings.SWITCH_PASSWORD,
|
|
||||||
timeout=settings.SWITCH_TIMEOUT,
|
|
||||||
ensp_mode=request.is_ensp
|
|
||||||
)
|
|
||||||
|
|
||||||
# 直接发送命令到交换机
|
|
||||||
result = await configurator.execute_raw_commands(
|
|
||||||
ip=request.switch_ip,
|
|
||||||
commands=request.commands
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"output": result,
|
|
||||||
"mode": "eNSP" if request.is_ensp else "SSH"
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(500, detail=str(e))
|
|
||||||
|
|
||||||
@router.get("/traffic/interfaces", summary="获取所有网络接口")
|
@router.get("/traffic/interfaces", summary="获取所有网络接口")
|
||||||
async def get_network_interfaces():
|
async def get_network_interfaces():
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -179,13 +179,6 @@ class SwitchConfigurator:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise SSHConnectionException(f"连接异常: {str(e)}")
|
raise SSHConnectionException(f"连接异常: {str(e)}")
|
||||||
|
|
||||||
async def execute_raw_commands(self, ip: str, commands: List[str]) -> str:
|
|
||||||
"""
|
|
||||||
执行原始CLI命令
|
|
||||||
"""
|
|
||||||
return await self._send_commands(ip, commands)
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_standard_commands(config: SwitchConfig) -> List[str]:
|
def _generate_standard_commands(config: SwitchConfig) -> List[str]:
|
||||||
"""生成标准CLI命令"""
|
"""生成标准CLI命令"""
|
||||||
@ -249,7 +242,6 @@ class SwitchConfigurator:
|
|||||||
logging.error(f"恢复失败: {str(e)}")
|
logging.error(f"恢复失败: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@retry(
|
@retry(
|
||||||
stop=stop_after_attempt(2),
|
stop=stop_after_attempt(2),
|
||||||
wait=wait_exponential(multiplier=1, min=4, max=10)
|
wait=wait_exponential(multiplier=1, min=4, max=10)
|
||||||
|
|||||||
@ -1,72 +1,64 @@
|
|||||||
from typing import Dict, Any, Coroutine
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from openai import OpenAI
|
|
||||||
import json
|
import json
|
||||||
|
import httpx
|
||||||
|
from typing import Dict, Any
|
||||||
from src.backend.app.utils.exceptions import SiliconFlowAPIException
|
from src.backend.app.utils.exceptions import SiliconFlowAPIException
|
||||||
from openai.types.chat import ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam
|
|
||||||
from src.backend.app.utils.logger import logger
|
|
||||||
|
|
||||||
|
|
||||||
class AIService:
|
class AIService:
|
||||||
def __init__(self, api_key: str, api_url: str):
|
def __init__(self, api_key: str, api_url: str):
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.api_url = api_url
|
self.api_url = api_url
|
||||||
self.client = OpenAI(
|
self.headers = {
|
||||||
api_key=self.api_key,
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
base_url=self.api_url,
|
"Content-Type": "application/json"
|
||||||
# timeout=httpx.Timeout(30.0)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
async def parse_command(self, command: str) -> Any | None:
|
async def parse_command(self, command: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
调用硅基流动API解析中文命令
|
调用硅基流动API解析中文命令
|
||||||
"""
|
"""
|
||||||
prompt = """
|
prompt = f"""
|
||||||
你是一个网络设备配置专家,精通各种类型的路由器的配置,请将以下用户的中文命令转换为网络设备配置JSON
|
你是一个网络设备配置专家,请将以下中文命令转换为网络设备配置JSON。
|
||||||
但是请注意,由于贪婪的人们追求极高的效率,所以你必须严格按照 JSON 格式返回数据,不要包含任何额外文本或 Markdown 代码块
|
支持的配置包括:VLAN、端口、路由、ACL等。
|
||||||
返回格式要求:
|
返回格式必须为JSON,包含配置类型和详细参数。
|
||||||
1. 必须包含'type'字段指明配置类型(vlan/interface/acl/route等)
|
|
||||||
2. 必须包含'commands'字段,包含可直接执行的命令列表
|
命令:{command}
|
||||||
3. 其他参数根据配置类型动态添加
|
|
||||||
4. 不要包含解释性文本、步骤说明或注释
|
|
||||||
示例命令:'创建VLAN 100,名称为TEST'
|
|
||||||
示例返回:{"type": "vlan", "vlan_id": 100, "name": "TEST", "commands": ["vlan 100", "name TEST"]}
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
messages = [
|
data = {
|
||||||
ChatCompletionSystemMessageParam(role="system", content=prompt),
|
"model": "deepseek-ai/DeepSeek-V3",
|
||||||
ChatCompletionUserMessageParam(role="user", content=command)
|
"prompt": prompt,
|
||||||
]
|
"max_tokens": 1000,
|
||||||
|
"temperature": 0.3
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = self.client.chat.completions.create(
|
async with httpx.AsyncClient() as client:
|
||||||
model="deepseek-ai/DeepSeek-V3",
|
response = await client.post(
|
||||||
messages=messages,
|
f"{self.api_url}/chat/completions",
|
||||||
temperature=0.3,
|
headers=self.headers,
|
||||||
max_tokens=1000,
|
json=data,
|
||||||
response_format={"type": "json_object"}
|
timeout=30
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(response)
|
if response.status_code != 200:
|
||||||
|
raise SiliconFlowAPIException(response.text)
|
||||||
|
|
||||||
config_str = response.choices[0].message.content.strip()
|
result = response.json()
|
||||||
|
config_str = result["choices"][0]["text"].strip()
|
||||||
|
|
||||||
try:
|
# 确保返回的是有效的JSON
|
||||||
config = json.loads(config_str)
|
try:
|
||||||
return config
|
config = json.loads(config_str)
|
||||||
except json.JSONDecodeError:
|
return config
|
||||||
# 尝试修复可能的多余字符
|
except json.JSONDecodeError:
|
||||||
if config_str.startswith("```json"):
|
# 尝试修复可能的多余字符
|
||||||
config_str = config_str[7:-3].strip()
|
if config_str.startswith("```json"):
|
||||||
return json.loads(config_str)
|
config_str = config_str[7:-3].strip()
|
||||||
raise SiliconFlowAPIException("Invalid JSON format returned from AI")
|
return json.loads(config_str)
|
||||||
except KeyError:
|
raise SiliconFlowAPIException("Invalid JSON format returned from AI")
|
||||||
logger.error(KeyError)
|
except httpx.HTTPError as e:
|
||||||
raise SiliconFlowAPIException("errrrrrrro")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
raise SiliconFlowAPIException(
|
raise SiliconFlowAPIException(
|
||||||
detail=f"API请求失败: {str(e)}",
|
detail=f"API请求失败: {str(e)}",
|
||||||
status_code=getattr(e, "status_code", 500)
|
status_code=e.response.status_code if hasattr(e, "response") else 500
|
||||||
)
|
)
|
||||||
@ -1,26 +1,34 @@
|
|||||||
|
# 核心依赖 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
fastapi==0.110.0
|
fastapi==0.110.0
|
||||||
uvicorn==0.29.0
|
uvicorn==0.29.0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
pysnmp
|
|
||||||
|
# Pydantic 模型 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
pydantic==2.6.4
|
pydantic==2.6.4
|
||||||
pydantic-settings==2.2.1
|
pydantic-settings==2.2.1
|
||||||
openai==1.93.2
|
|
||||||
|
# 网络操作 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
asyncssh==2.14.2
|
asyncssh==2.14.2
|
||||||
telnetlib3==2.0.3
|
telnetlib3==2.0.3
|
||||||
httpx==0.27.0
|
httpx==0.27.0
|
||||||
python-nmap==0.7.1
|
python-nmap==0.7.1
|
||||||
pysnmp==7.1.21
|
|
||||||
|
|
||||||
|
# 异步文件操作 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
aiofiles==23.2.1
|
aiofiles==23.2.1
|
||||||
|
|
||||||
|
# 日志管理 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
loguru==0.7.2
|
loguru==0.7.2
|
||||||
|
|
||||||
|
# 重试机制 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
tenacity==8.2.3
|
tenacity==8.2.3
|
||||||
|
|
||||||
|
# 其他工具 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
asyncio==3.4.3
|
asyncio==3.4.3
|
||||||
typing_extensions==4.10.0
|
typing_extensions==4.10.0
|
||||||
|
|
||||||
|
#监控依赖 Y
|
||||||
|
|
||||||
psutil==5.9.8
|
psutil==5.9.8
|
||||||
matplotlib==3.8.3
|
matplotlib==3.8.3
|
||||||
sqlalchemy==2.0.28
|
sqlalchemy==2.0.28
|
||||||
sqlalchemy
|
|
||||||
openai
|
|
||||||
@ -96,7 +96,7 @@ const ConfigPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleApply = async () => {
|
const handleApply = async () => {
|
||||||
if (!editableConfig) {
|
if (!editableConfig.trim()) {
|
||||||
Notification.warn({
|
Notification.warn({
|
||||||
title: '配置为空',
|
title: '配置为空',
|
||||||
description: '请先解析或编辑有效配置',
|
description: '请先解析或编辑有效配置',
|
||||||
@ -213,15 +213,13 @@ const ConfigPage = () => {
|
|||||||
生成配置:
|
生成配置:
|
||||||
</Text>
|
</Text>
|
||||||
<Textarea
|
<Textarea
|
||||||
value={JSON.stringify(editableConfig)}
|
value={editableConfig}
|
||||||
rows={12}
|
rows={12}
|
||||||
onChange={(e) => setEditableConfig(e.target.value)}
|
onChange={(e) => setEditableConfig(e.target.value)}
|
||||||
fontFamily={'monospace'}
|
fontFamily={'monospace'}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
bg={'blackAlpha.200'}
|
bg={'blackAlpha.200'}
|
||||||
whiteSpace="pre-wrap"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HStack mt={4} spacing={3} justify={'flex-end'}>
|
<HStack mt={4} spacing={3} justify={'flex-end'}>
|
||||||
<Button
|
<Button
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
@ -242,7 +240,7 @@ const ConfigPage = () => {
|
|||||||
size={'sm'}
|
size={'sm'}
|
||||||
onClick={handleApply}
|
onClick={handleApply}
|
||||||
isLoading={applying}
|
isLoading={applying}
|
||||||
isDisabled={!editableConfig}
|
isDisabled={!editableConfig.trim()}
|
||||||
>
|
>
|
||||||
应用到交换机
|
应用到交换机
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -41,13 +41,7 @@ export const api = {
|
|||||||
* @param text 文本
|
* @param text 文本
|
||||||
* @returns {Promise<axios.AxiosResponse<any>>}
|
* @returns {Promise<axios.AxiosResponse<any>>}
|
||||||
*/
|
*/
|
||||||
//parseCommand: async (text) => await axios.post(buildUrl('/api/parse_command'), { command: text }),
|
parseCommand: (text) => axios.post(buildUrl('/api/parse_command'), { command: text }),
|
||||||
async parseCommand(text) {
|
|
||||||
const res = await axios.post(buildUrl('/api/parse_command', { command: text }));
|
|
||||||
if (res) {
|
|
||||||
return res;
|
|
||||||
} else return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用配置
|
* 应用配置
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user