mirror of
https://github.com/alibaba/higress.git
synced 2026-03-07 10:00:48 +08:00
feat: impl nginx migration mcp server (#2916)
Co-authored-by: 韩贤涛 <601803023@qq.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/registry/nacos"
|
||||
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/gorm"
|
||||
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/higress-api"
|
||||
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/higress/nginx-migration"
|
||||
_ "github.com/alibaba/higress/plugins/golang-filter/mcp-server/servers/rag"
|
||||
mcp_session "github.com/alibaba/higress/plugins/golang-filter/mcp-session"
|
||||
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
|
||||
|
||||
2
plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.gitignore
vendored
Normal file
2
plugins/golang-filter/mcp-server/servers/higress/nginx-migration/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# 本地配置文件
|
||||
config/rag.json
|
||||
@@ -0,0 +1,59 @@
|
||||
.PHONY: all build standalone clean test help
|
||||
|
||||
# 默认目标
|
||||
all: standalone
|
||||
|
||||
# build 别名(指向 standalone)
|
||||
build: standalone
|
||||
|
||||
# 编译独立模式
|
||||
standalone:
|
||||
@echo "编译独立模式..."
|
||||
@cd standalone/cmd && go build -o ../../nginx-migration-mcp
|
||||
@echo "独立模式编译完成: ./nginx-migration-mcp"
|
||||
|
||||
# 清理编译产物
|
||||
clean:
|
||||
@echo "清理编译产物..."
|
||||
@rm -f nginx-migration-mcp
|
||||
@echo "清理完成"
|
||||
|
||||
# 运行测试
|
||||
test:
|
||||
@echo "运行测试..."
|
||||
@go test ./...
|
||||
@echo "测试完成"
|
||||
|
||||
|
||||
# 安装依赖
|
||||
deps:
|
||||
@echo "安装依赖..."
|
||||
@go mod tidy
|
||||
@echo "依赖安装完成"
|
||||
|
||||
# 格式化代码
|
||||
fmt:
|
||||
@echo "格式化代码..."
|
||||
@go fmt ./...
|
||||
@echo "代码格式化完成"
|
||||
|
||||
# 显示帮助
|
||||
help:
|
||||
@echo "Nginx Migration MCP Server - Makefile"
|
||||
@echo ""
|
||||
@echo "可用目标:"
|
||||
@echo " make - 编译独立模式(默认)"
|
||||
@echo " make build - 编译独立模式"
|
||||
@echo " make standalone - 编译独立模式"
|
||||
@echo " make test - 运行测试"
|
||||
@echo " make clean - 清理编译产物"
|
||||
@echo " make deps - 安装依赖"
|
||||
@echo " make fmt - 格式化代码"
|
||||
@echo " make help - 显示此帮助信息"
|
||||
@echo ""
|
||||
@echo "目录结构:"
|
||||
@echo " cmd/standalone/ - 独立模式入口"
|
||||
@echo " internal/standalone/ - 独立模式实现"
|
||||
@echo " integration/ - Higress MCP 框架集成"
|
||||
@echo " tools/ - 共享核心逻辑"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# Nginx Migration MCP 快速开始
|
||||
|
||||
|
||||
### 1. 构建服务器
|
||||
|
||||
```bash
|
||||
cd /path/to/higress/plugins/golang-filter/mcp-server/servers/higress/nginx-migration
|
||||
make build
|
||||
```
|
||||
|
||||
### 2. 配置 MCP 客户端
|
||||
|
||||
在 MCP 客户端配置文件中添加(以 Cursor 为例):
|
||||
|
||||
**位置**: `~/.cursor/config/mcp_settings.json` 或 Cursor 设置中的 MCP 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"nginx-migration": {
|
||||
"command": "/path/to/nginx-migration/nginx-migration-mcp",
|
||||
"args": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 可用工具
|
||||
|
||||
### Nginx 配置转换
|
||||
|
||||
`parse_nginx_config` | 解析和分析 Nginx 配置文件 |
|
||||
`convert_to_higress` | 转换为 Higress HTTPRoute 和 Service |
|
||||
|
||||
### Lua 插件迁移
|
||||
|
||||
|
||||
`convert_lua_to_wasm` | 一键转换 Lua 脚本为 WASM 插件 |
|
||||
`analyze_lua_plugin` | 分析 Lua 插件兼容性 |
|
||||
`generate_conversion_hints` | 生成 API 映射和转换提示 |
|
||||
`validate_wasm_code` | 验证 Go WASM 代码 |
|
||||
`generate_deployment_config` | 生成部署配置包 |
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1:转换 Nginx 配置
|
||||
|
||||
```
|
||||
我有一个 Nginx 配置,帮我转换为 Higress HTTPRoute
|
||||
```
|
||||
|
||||
HOST LLM 会自动调用 `convert_to_higress` 工具完成转换。
|
||||
|
||||
### 示例 2:快速转换 Lua 插件
|
||||
|
||||
```
|
||||
将这个 Lua 限流插件转换为 Higress WASM 插件:
|
||||
[粘贴 Lua 代码]
|
||||
```
|
||||
|
||||
HOST LLM 会调用 `convert_lua_to_wasm` 工具自动转换。
|
||||
|
||||
### 示例 3:使用工具链精细转换
|
||||
|
||||
```
|
||||
分析这个 Lua 插件的兼容性:
|
||||
[粘贴 Lua 代码]
|
||||
```
|
||||
|
||||
然后按照工具链流程:
|
||||
1. LLM 调用 `analyze_lua_plugin` 分析
|
||||
2. LLM 调用 `generate_conversion_hints` 获取转换提示
|
||||
3. LLM 基于提示生成 Go WASM 代码
|
||||
4. LLM 调用 `validate_wasm_code` 验证代码
|
||||
5. LLM 调用 `generate_deployment_config` 生成部署配置
|
||||
|
||||
## 调试
|
||||
|
||||
启用调试日志:
|
||||
|
||||
```bash
|
||||
DEBUG=true ./nginx-migration-mcp
|
||||
```
|
||||
|
||||
查看工具列表:
|
||||
|
||||
```bash
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | ./nginx-migration-mcp
|
||||
```
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
# Nginx Migration MCP Server
|
||||
|
||||
一个用于将 Nginx 配置和 Lua 插件迁移到 Higress 的 MCP 服务器。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 配置转换工具
|
||||
- **parse_nginx_config** - 解析和分析 Nginx 配置文件
|
||||
- **convert_to_higress** - 将 Nginx 配置转换为 Higress Ingress(主要方式)或 HTTPRoute(可选)
|
||||
|
||||
### Lua 插件迁移工具链
|
||||
|
||||
#### 快速转换模式
|
||||
- **convert_lua_to_wasm** - 一键将 Lua 脚本转换为 WASM 插件
|
||||
|
||||
#### LLM 辅助工具链(精细化控制)
|
||||
1. **analyze_lua_plugin** - 分析 Lua 插件兼容性
|
||||
2. **generate_conversion_hints** - 生成详细的代码转换提示和 API 映射
|
||||
3. **validate_wasm_code** - 验证生成的 Go WASM 代码
|
||||
4. **generate_deployment_config** - 生成完整的部署配置包
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 构建
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
构建后会生成 `nginx-migration-mcp` 可执行文件。
|
||||
|
||||
### 基础配置(无需知识库)
|
||||
|
||||
**默认模式**:工具可以直接使用,基于内置规则生成转换建议。
|
||||
|
||||
在 MCP 客户端(如 Cursor)的配置文件中添加:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"nginx-migration": {
|
||||
"command": "/path/to/nginx-migration-mcp",
|
||||
"args": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 进阶配置(启用 RAG 知识库)
|
||||
|
||||
RAG(检索增强生成)功能通过阿里云百炼集成 Higress 官方文档知识库,提供更准确的转换建议和 API 映射。
|
||||
|
||||
#### 适用场景
|
||||
|
||||
启用 RAG 后,以下工具将获得增强:
|
||||
- **generate_conversion_hints** - 提供基于官方文档的 API 映射和代码示例
|
||||
- **validate_wasm_code** - 基于最佳实践验证代码质量
|
||||
- **query_knowledge_base** - 直接查询 Higress 官方文档
|
||||
|
||||
#### 配置步骤
|
||||
|
||||
**步骤 1:获取阿里云百炼凭证**
|
||||
|
||||
1. 访问 [阿里云百炼控制台](https://bailian.console.aliyun.com/)
|
||||
2. 创建或选择一个应用空间,获取 **业务空间 ID** (`workspace_id`)
|
||||
3. 创建知识库并导入 Higress 文档,获取 **知识库 ID** (`knowledge_base_id`)
|
||||
4. 在 [阿里云 RAM 控制台](https://ram.console.aliyun.com/manage/ak) 创建 AccessKey
|
||||
- 获取 **AccessKey ID** (`access_key_id`)
|
||||
- 获取 **AccessKey Secret** (`access_key_secret`)
|
||||
|
||||
> **安全提示**:请妥善保管 AccessKey,避免泄露。建议使用 RAM 子账号并授予最小权限。
|
||||
|
||||
**步骤 2:复制配置文件**
|
||||
```bash
|
||||
cp config/rag.json.example config/rag.json
|
||||
```
|
||||
|
||||
**步骤 3:编辑配置文件**
|
||||
|
||||
有两种配置方式:
|
||||
|
||||
**方式 1:配置 rag.config**
|
||||
```json
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"rag": {
|
||||
"enabled": true,
|
||||
"provider": "bailian",
|
||||
"endpoint": "bailian.cn-beijing.aliyuncs.com",
|
||||
"workspace_id": "${WORKSPACE_ID}",
|
||||
"knowledge_base_id": "${INDEX_ID}",
|
||||
"access_key_id": "${ALIBABA_CLOUD_ACCESS_KEY_ID}",
|
||||
"access_key_secret": "${ALIBABA_CLOUD_ACCESS_KEY_SECRET}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 高级配置项
|
||||
|
||||
完整的配置选项(可选):
|
||||
|
||||
```json
|
||||
{
|
||||
"rag": {
|
||||
"enabled": true,
|
||||
|
||||
// === 必填:API 配置 ===
|
||||
"provider": "bailian",
|
||||
"endpoint": "bailian.cn-beijing.aliyuncs.com",
|
||||
"workspace_id": "llm-xxx",
|
||||
"knowledge_base_id": "idx-xxx",
|
||||
"access_key_id": "LTAI5t...",
|
||||
"access_key_secret": "your-secret",
|
||||
|
||||
// === 可选:检索配置 ===
|
||||
"context_mode": "full", // 上下文模式: full | summary | highlights
|
||||
"max_context_length": 4000, // 最大上下文长度(字符)
|
||||
"default_top_k": 3, // 默认返回文档数量
|
||||
"similarity_threshold": 0.7, // 相似度阈值(0-1)
|
||||
|
||||
// === 可选:性能配置 ===
|
||||
"enable_cache": true, // 启用查询缓存
|
||||
"cache_ttl": 3600, // 缓存过期时间(秒)
|
||||
"cache_max_size": 1000, // 最大缓存条目数
|
||||
"timeout": 10, // 请求超时(秒)
|
||||
"max_retries": 3, // 最大重试次数
|
||||
"retry_delay": 1, // 重试间隔(秒)
|
||||
|
||||
// === 可选:降级策略 ===
|
||||
"fallback_on_error": true, // RAG 失败时降级到基础模式
|
||||
|
||||
// === 可选:工具级配置 ===
|
||||
"tools": {
|
||||
"generate_conversion_hints": {
|
||||
"use_rag": true, // 为此工具启用 RAG
|
||||
"context_mode": "full",
|
||||
"top_k": 3
|
||||
},
|
||||
"validate_wasm_code": {
|
||||
"use_rag": true,
|
||||
"context_mode": "highlights",
|
||||
"top_k": 2
|
||||
}
|
||||
},
|
||||
|
||||
// === 可选:调试配置 ===
|
||||
"debug": true, // 启用调试日志
|
||||
"log_queries": true // 记录所有查询
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 验证配置
|
||||
|
||||
启动服务后,检查日志输出:
|
||||
|
||||
```bash
|
||||
# 正常启用 RAG
|
||||
✓ RAG enabled: bailian (endpoint: bailian.cn-beijing.aliyuncs.com)
|
||||
✓ Knowledge base: idx-xxx
|
||||
✓ Cache enabled (TTL: 3600s, Max: 1000)
|
||||
|
||||
# RAG 未启用
|
||||
⚠ RAG disabled, using rule-based conversion only
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 转换 Nginx 配置
|
||||
|
||||
使用 `convert_to_higress` 工具,传入 Nginx 配置内容:
|
||||
- **默认**:生成 Kubernetes Ingress 和 Service 资源
|
||||
- **可选**:设置 `use_gateway_api=true` 生成 Gateway API HTTPRoute(需确认已启用)
|
||||
|
||||
|
||||
### 迁移 Lua 插件
|
||||
|
||||
**方式一:快速转换**
|
||||
|
||||
使用 `convert_lua_to_wasm` 工具一键转换 Lua 脚本为 WASM 插件。
|
||||
|
||||
**方式二:AI 辅助工具链**
|
||||
|
||||
1. 使用 `analyze_lua_plugin` 分析 Lua 代码
|
||||
2. 使用 `generate_conversion_hints` 获取转换提示和 API 映射(可启用 RAG 增强)
|
||||
3. AI 根据提示生成 Go WASM 代码
|
||||
4. 使用 `validate_wasm_code` 验证生成的代码(可启用 RAG 增强)
|
||||
5. 使用 `generate_deployment_config` 生成部署配置
|
||||
|
||||
推荐使用工具链方式处理复杂插件,可获得更好的转换质量和 AI 辅助。
|
||||
|
||||
### 查询知识库(需启用 RAG)
|
||||
|
||||
使用 `query_knowledge_base` 工具直接查询 Higress 文档:
|
||||
|
||||
```javascript
|
||||
// 查询 Lua API 迁移方法
|
||||
query_knowledge_base({
|
||||
"query": "ngx.req.get_headers 在 Higress 中怎么实现?",
|
||||
"scenario": "lua_migration",
|
||||
"top_k": 5
|
||||
})
|
||||
|
||||
// 查询插件配置方法
|
||||
query_knowledge_base({
|
||||
"query": "Higress 限流插件配置",
|
||||
"scenario": "config_conversion",
|
||||
"top_k": 3
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
nginx-migration/
|
||||
├── config/ # 配置文件
|
||||
│ ├── rag.json.example # RAG 配置示例
|
||||
│ └── rag.json # RAG 配置(需自行创建)
|
||||
│
|
||||
├── integration/ # Higress 集成模式(MCP 集成)
|
||||
│ ├── server.go # MCP 服务器注册与初始化
|
||||
│ └── mcptools/ # MCP 工具实现
|
||||
│ ├── adapter.go # MCP 工具适配器
|
||||
│ ├── context.go # 迁移上下文管理
|
||||
│ ├── nginx_tools.go # Nginx 配置转换工具
|
||||
│ ├── lua_tools.go # Lua 插件迁移工具
|
||||
│ ├── tool_chain.go # 工具链实现(分析、验证、部署)
|
||||
│ └── rag_integration.go # RAG 知识库集成
|
||||
│
|
||||
├── standalone/ # 独立模式(可独立运行)
|
||||
│ ├── cmd/
|
||||
│ │ └── main.go # 独立模式入口
|
||||
│ ├── server.go # 独立模式 MCP 服务器
|
||||
│ ├── config.go # 配置加载
|
||||
│ └── types.go # 类型定义
|
||||
│
|
||||
├── internal/ # 内部实现包
|
||||
│ ├── rag/ # RAG 功能实现
|
||||
│ │ ├── config.go # RAG 配置结构
|
||||
│ │ ├── client.go # 百炼 API 客户端
|
||||
│ │ └── manager.go # RAG 管理器(查询、缓存)
|
||||
│ └── standalone/ # 独立模式内部实现
|
||||
│ └── server.go # 独立服务器逻辑
|
||||
│
|
||||
├── tools/ # 核心转换逻辑(共享库)
|
||||
│ ├── mcp_tools.go # MCP 工具定义和注册
|
||||
│ ├── nginx_parser.go # Nginx 配置解析器
|
||||
│ ├── lua_converter.go # Lua 到 WASM 转换器
|
||||
│ └── tool_chain.go # 工具链核心实现
|
||||
│
|
||||
├── docs/ # 文档目录
|
||||
│
|
||||
├── mcp-tools.json # MCP 工具元数据定义
|
||||
├── go.mod # Go 模块依赖
|
||||
├── go.sum # Go 模块校验和
|
||||
├── Makefile # 构建脚本
|
||||
│
|
||||
├── README.md # 项目说明文档
|
||||
├── QUICKSTART.md # 快速开始指南
|
||||
├── QUICK_TEST.md # 快速测试指南
|
||||
├── TEST_EXAMPLES.md # 测试示例
|
||||
└── CHANGELOG_INGRESS_PRIORITY.md # Ingress 优先级变更记录
|
||||
```
|
||||
|
||||
### 目录说明
|
||||
|
||||
#### 核心模块
|
||||
|
||||
- **`integration/`** - Higress 集成模式
|
||||
- 作为 Higress MCP 服务器的一部分运行
|
||||
- 提供完整的 MCP 工具集成
|
||||
- 支持 RAG 知识库增强
|
||||
|
||||
- **`standalone/`** - 独立模式
|
||||
- 可独立运行的 MCP 服务器
|
||||
- 适合本地开发和测试
|
||||
- 支持相同的工具功能
|
||||
|
||||
- **`tools/`** - 核心转换逻辑
|
||||
- 独立于运行模式的转换引擎
|
||||
- 包含 Nginx 解析、Lua 转换等核心功能
|
||||
- 可被集成模式和独立模式复用
|
||||
|
||||
- **`internal/rag/`** - RAG 功能实现
|
||||
- 阿里云百炼 API 客户端
|
||||
- 知识库查询和结果处理
|
||||
- 缓存管理和性能优化
|
||||
|
||||
|
||||
#### 配置文件
|
||||
|
||||
- **`config/rag.json`** - RAG 功能配置(需从 example 复制并填写凭证)
|
||||
- **`mcp-tools.json`** - MCP 工具元数据定义(工具描述、参数 schema)
|
||||
|
||||
## 开发
|
||||
|
||||
### 构建命令
|
||||
|
||||
```bash
|
||||
# 编译
|
||||
make build
|
||||
|
||||
# 清理
|
||||
make clean
|
||||
|
||||
# 格式化代码
|
||||
make fmt
|
||||
|
||||
# 运行测试
|
||||
make test
|
||||
|
||||
# 查看帮助
|
||||
make help
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"rag": {
|
||||
"enabled": true,
|
||||
"provider": "bailian",
|
||||
"endpoint": "bailian.cn-beijing.aliyuncs.com",
|
||||
"workspace_id": "${WORKSPACE_ID}",
|
||||
"knowledge_base_id": "${INDEX_ID}",
|
||||
"access_key_id": "${ALIBABA_CLOUD_ACCESS_KEY_ID}",
|
||||
"access_key_secret": "${ALIBABA_CLOUD_ACCESS_KEY_SECRET}",
|
||||
|
||||
"context_mode": "full",
|
||||
"max_context_length": 4000,
|
||||
"default_top_k": 3,
|
||||
"similarity_threshold": 0.7,
|
||||
|
||||
"enable_cache": true,
|
||||
"cache_ttl": 3600,
|
||||
"cache_max_size": 1000,
|
||||
|
||||
"timeout": 10,
|
||||
"max_retries": 3,
|
||||
"retry_delay": 1,
|
||||
|
||||
"fallback_on_error": true,
|
||||
|
||||
"tools": {
|
||||
"generate_conversion_hints": {
|
||||
"use_rag": true,
|
||||
"context_mode": "full",
|
||||
"top_k": 3
|
||||
},
|
||||
"validate_wasm_code": {
|
||||
"use_rag": true,
|
||||
"context_mode": "highlights",
|
||||
"top_k": 2
|
||||
},
|
||||
"convert_lua_to_wasm": {
|
||||
"use_rag": false
|
||||
}
|
||||
},
|
||||
|
||||
"debug": true,
|
||||
"log_queries": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
module nginx-migration-mcp
|
||||
|
||||
go 1.23
|
||||
|
||||
toolchain go1.24.9
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/golang-filter v0.0.0-20251023035326-7ea739292dea
|
||||
github.com/alibabacloud-go/bailian-20231229/v2 v2.5.0
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13
|
||||
github.com/alibabacloud-go/tea v1.3.13
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
||||
github.com/envoyproxy/envoy v1.36.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.5 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mark3labs/mcp-go v0.12.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,60 @@
|
||||
//go:build higress_integration
|
||||
// +build higress_integration
|
||||
|
||||
package mcptools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
// SimpleToolHandler is a simplified handler function that takes arguments and returns a string result
|
||||
type SimpleToolHandler func(args map[string]interface{}) (string, error)
|
||||
|
||||
// AdaptSimpleHandler converts a SimpleToolHandler to an MCP ToolHandlerFunc
|
||||
func AdaptSimpleHandler(handler SimpleToolHandler) common.ToolHandlerFunc {
|
||||
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// Extract arguments
|
||||
var args map[string]interface{}
|
||||
if request.Params.Arguments != nil {
|
||||
args = request.Params.Arguments
|
||||
} else {
|
||||
args = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Call the simple handler
|
||||
result, err := handler(args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tool execution failed: %w", err)
|
||||
}
|
||||
|
||||
// Return MCP result
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: result,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterSimpleTool registers a tool with a simplified handler
|
||||
func RegisterSimpleTool(
|
||||
server *common.MCPServer,
|
||||
name string,
|
||||
description string,
|
||||
inputSchema map[string]interface{},
|
||||
handler SimpleToolHandler,
|
||||
) {
|
||||
// Create tool with schema
|
||||
schemaBytes, _ := json.Marshal(inputSchema)
|
||||
|
||||
tool := mcp.NewToolWithRawSchema(name, description, schemaBytes)
|
||||
server.AddTool(tool, AdaptSimpleHandler(handler))
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//go:build higress_integration
|
||||
// +build higress_integration
|
||||
|
||||
package mcptools
|
||||
|
||||
import (
|
||||
"log"
|
||||
"nginx-migration-mcp/internal/rag"
|
||||
)
|
||||
|
||||
// MigrationContext holds the configuration context for migration operations
|
||||
type MigrationContext struct {
|
||||
GatewayName string
|
||||
GatewayNamespace string
|
||||
DefaultNamespace string
|
||||
DefaultHostname string
|
||||
RoutePrefix string
|
||||
ServicePort int
|
||||
TargetPort int
|
||||
RAGManager *rag.RAGManager // RAG 管理器
|
||||
}
|
||||
|
||||
// NewDefaultMigrationContext creates a MigrationContext with default values
|
||||
func NewDefaultMigrationContext() *MigrationContext {
|
||||
return &MigrationContext{
|
||||
GatewayName: "higress-gateway",
|
||||
GatewayNamespace: "higress-system",
|
||||
DefaultNamespace: "default",
|
||||
DefaultHostname: "example.com",
|
||||
RoutePrefix: "nginx-migrated",
|
||||
ServicePort: 80,
|
||||
TargetPort: 8080,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMigrationContextWithRAG creates a MigrationContext with RAG support
|
||||
func NewMigrationContextWithRAG(ragConfigPath string) *MigrationContext {
|
||||
ctx := NewDefaultMigrationContext()
|
||||
|
||||
// 加载 RAG 配置
|
||||
config, err := rag.LoadRAGConfig(ragConfigPath)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to load RAG config: %v, RAG will be disabled", err)
|
||||
config = &rag.RAGConfig{Enabled: false}
|
||||
}
|
||||
|
||||
// 创建 RAG 管理器
|
||||
ctx.RAGManager = rag.NewRAGManager(config)
|
||||
|
||||
if ctx.RAGManager.IsEnabled() {
|
||||
log.Println("✅ MigrationContext: RAG enabled")
|
||||
} else {
|
||||
log.Println("📖 MigrationContext: RAG disabled, using rule-based approach")
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
//go:build higress_integration
|
||||
// +build higress_integration
|
||||
|
||||
package mcptools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"nginx-migration-mcp/internal/rag"
|
||||
"nginx-migration-mcp/tools"
|
||||
|
||||
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
|
||||
)
|
||||
|
||||
// RegisterLuaPluginTools registers Lua plugin analysis and conversion tools
|
||||
func RegisterLuaPluginTools(server *common.MCPServer, ctx *MigrationContext) {
|
||||
RegisterSimpleTool(
|
||||
server,
|
||||
"analyze_lua_plugin",
|
||||
"分析 Nginx Lua 插件的兼容性,识别使用的 API 和潜在迁移问题",
|
||||
map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"lua_code": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "Nginx Lua 插件代码",
|
||||
},
|
||||
},
|
||||
"required": []string{"lua_code"},
|
||||
},
|
||||
func(args map[string]interface{}) (string, error) {
|
||||
return analyzeLuaPlugin(args, ctx)
|
||||
},
|
||||
)
|
||||
|
||||
RegisterSimpleTool(
|
||||
server,
|
||||
"convert_lua_to_wasm",
|
||||
"一键将 Nginx Lua 脚本转换为 Higress WASM 插件,自动生成 Go 代码和配置",
|
||||
map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"lua_code": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "要转换的 Nginx Lua 插件代码",
|
||||
},
|
||||
"plugin_name": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "生成的 WASM 插件名称 (小写字母和连字符)",
|
||||
},
|
||||
},
|
||||
"required": []string{"lua_code", "plugin_name"},
|
||||
},
|
||||
func(args map[string]interface{}) (string, error) {
|
||||
return convertLuaToWasm(args, ctx)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func analyzeLuaPlugin(args map[string]interface{}, ctx *MigrationContext) (string, error) {
|
||||
luaCode, ok := args["lua_code"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid lua_code parameter")
|
||||
}
|
||||
|
||||
// Analyze Lua features (基于规则)
|
||||
features := []string{}
|
||||
warnings := []string{}
|
||||
detectedAPIs := []string{}
|
||||
|
||||
if strings.Contains(luaCode, "ngx.var") {
|
||||
features = append(features, "- ngx.var - Nginx变量")
|
||||
detectedAPIs = append(detectedAPIs, "ngx.var")
|
||||
}
|
||||
if strings.Contains(luaCode, "ngx.req") {
|
||||
features = append(features, "- ngx.req - 请求API")
|
||||
detectedAPIs = append(detectedAPIs, "ngx.req")
|
||||
}
|
||||
if strings.Contains(luaCode, "ngx.exit") {
|
||||
features = append(features, "- ngx.exit - 请求终止")
|
||||
detectedAPIs = append(detectedAPIs, "ngx.exit")
|
||||
}
|
||||
if strings.Contains(luaCode, "ngx.shared") {
|
||||
features = append(features, "- ngx.shared - 共享字典 (警告)")
|
||||
warnings = append(warnings, "共享字典需要外部缓存替换")
|
||||
detectedAPIs = append(detectedAPIs, "ngx.shared")
|
||||
}
|
||||
if strings.Contains(luaCode, "ngx.location.capture") {
|
||||
features = append(features, "- ngx.location.capture - 内部请求 (警告)")
|
||||
warnings = append(warnings, "需要改为HTTP客户端调用")
|
||||
detectedAPIs = append(detectedAPIs, "ngx.location.capture")
|
||||
}
|
||||
|
||||
compatibility := "full"
|
||||
if len(warnings) > 0 {
|
||||
compatibility = "partial"
|
||||
}
|
||||
if len(warnings) > 2 {
|
||||
compatibility = "manual"
|
||||
}
|
||||
|
||||
// === RAG 增强:查询知识库获取转换建议 ===
|
||||
var ragContext *rag.RAGContext
|
||||
if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() && len(detectedAPIs) > 0 {
|
||||
query := fmt.Sprintf("Nginx Lua API %s 在 Higress WASM 中的转换方法和最佳实践", strings.Join(detectedAPIs, ", "))
|
||||
var err error
|
||||
ragContext, err = ctx.RAGManager.QueryForTool("analyze_lua_plugin", query, "lua_migration")
|
||||
if err != nil {
|
||||
log.Printf("⚠️ RAG query failed for analyze_lua_plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 构建结果
|
||||
var result strings.Builder
|
||||
|
||||
// RAG 上下文(如果有)
|
||||
if ragContext != nil && ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
result.WriteString("📚 知识库参考资料:\n\n")
|
||||
result.WriteString(ragContext.FormatContextForAI())
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// 基于规则的分析
|
||||
warningsText := "无"
|
||||
if len(warnings) > 0 {
|
||||
warningsText = strings.Join(warnings, "\n")
|
||||
}
|
||||
|
||||
result.WriteString(fmt.Sprintf(`Lua插件兼容性分析
|
||||
|
||||
检测特性:
|
||||
%s
|
||||
|
||||
兼容性警告:
|
||||
%s
|
||||
|
||||
兼容性级别: %s
|
||||
|
||||
迁移建议:`, strings.Join(features, "\n"), warningsText, compatibility))
|
||||
|
||||
switch compatibility {
|
||||
case "full":
|
||||
result.WriteString("\n- 可直接迁移到WASM插件")
|
||||
case "partial":
|
||||
result.WriteString("\n- 需要部分重构")
|
||||
case "manual":
|
||||
result.WriteString("\n- 需要手动重写")
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
func convertLuaToWasm(args map[string]interface{}, ctx *MigrationContext) (string, error) {
|
||||
luaCode, ok := args["lua_code"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid lua_code parameter")
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid plugin_name parameter")
|
||||
}
|
||||
|
||||
// 分析Lua脚本
|
||||
analyzer := tools.AnalyzeLuaScript(luaCode)
|
||||
|
||||
// === RAG 增强:查询转换模式和代码示例 ===
|
||||
var ragContext *rag.RAGContext
|
||||
if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() && len(analyzer.Features) > 0 {
|
||||
// 提取特性列表
|
||||
featureList := []string{}
|
||||
for feature := range analyzer.Features {
|
||||
featureList = append(featureList, feature)
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("将使用了 %s 的 Nginx Lua 插件转换为 Higress WASM Go 插件的代码示例",
|
||||
strings.Join(featureList, ", "))
|
||||
var err error
|
||||
ragContext, err = ctx.RAGManager.QueryForTool("convert_lua_to_wasm", query, "lua_to_wasm")
|
||||
if err != nil {
|
||||
log.Printf("⚠️ RAG query failed for convert_lua_to_wasm: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为WASM插件
|
||||
result, err := tools.ConvertLuaToWasm(analyzer, pluginName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("conversion failed: %w", err)
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
var response strings.Builder
|
||||
|
||||
// RAG 上下文(如果有)
|
||||
if ragContext != nil && ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
response.WriteString("📚 知识库代码示例:\n\n")
|
||||
response.WriteString(ragContext.FormatContextForAI())
|
||||
response.WriteString("\n---\n\n")
|
||||
}
|
||||
|
||||
response.WriteString(fmt.Sprintf(`Go 代码:
|
||||
%s
|
||||
|
||||
WasmPlugin 配置:
|
||||
%s
|
||||
|
||||
复杂度: %s, 特性: %d, 警告: %d`,
|
||||
result.GoCode,
|
||||
result.WasmPluginYAML,
|
||||
analyzer.Complexity,
|
||||
len(analyzer.Features),
|
||||
len(analyzer.Warnings)))
|
||||
|
||||
return response.String(), nil
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
//go:build higress_integration
|
||||
// +build higress_integration
|
||||
|
||||
package mcptools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"nginx-migration-mcp/internal/rag"
|
||||
"nginx-migration-mcp/tools"
|
||||
"strings"
|
||||
|
||||
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
|
||||
)
|
||||
|
||||
// RegisterNginxConfigTools 注册 Nginx 配置分析和转换工具
|
||||
func RegisterNginxConfigTools(server *common.MCPServer, ctx *MigrationContext) {
|
||||
RegisterSimpleTool(
|
||||
server,
|
||||
"parse_nginx_config",
|
||||
"解析和分析 Nginx 配置文件,识别配置结构和复杂度",
|
||||
map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"config_content": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "Nginx 配置文件内容",
|
||||
},
|
||||
},
|
||||
"required": []string{"config_content"},
|
||||
},
|
||||
func(args map[string]interface{}) (string, error) {
|
||||
return parseNginxConfig(args, ctx)
|
||||
},
|
||||
)
|
||||
|
||||
RegisterSimpleTool(
|
||||
server,
|
||||
"convert_to_higress",
|
||||
"将 Nginx 配置转换为 Higress Ingress 和 Service 资源(主要方式)或 HTTPRoute(可选)",
|
||||
map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"config_content": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "Nginx 配置文件内容",
|
||||
},
|
||||
"namespace": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "目标 Kubernetes 命名空间",
|
||||
"default": "default",
|
||||
},
|
||||
"use_gateway_api": map[string]interface{}{
|
||||
"type": "boolean",
|
||||
"description": "是否使用 Gateway API (HTTPRoute)。默认 false,使用 Ingress",
|
||||
"default": false,
|
||||
},
|
||||
},
|
||||
"required": []string{"config_content"},
|
||||
},
|
||||
func(args map[string]interface{}) (string, error) {
|
||||
return convertToHigress(args, ctx)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func parseNginxConfig(args map[string]interface{}, ctx *MigrationContext) (string, error) {
|
||||
configContent, ok := args["config_content"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid config_content parameter")
|
||||
}
|
||||
|
||||
// Simple analysis
|
||||
serverCount := strings.Count(configContent, "server {")
|
||||
locationCount := strings.Count(configContent, "location")
|
||||
hasSSL := strings.Contains(configContent, "ssl")
|
||||
hasProxy := strings.Contains(configContent, "proxy_pass")
|
||||
hasRewrite := strings.Contains(configContent, "rewrite")
|
||||
|
||||
complexity := "Simple"
|
||||
if serverCount > 1 || (hasRewrite && hasSSL) {
|
||||
complexity = "Complex"
|
||||
} else if hasRewrite || hasSSL {
|
||||
complexity = "Medium"
|
||||
}
|
||||
|
||||
// 收集配置特性用于 RAG 查询
|
||||
features := []string{}
|
||||
if hasProxy {
|
||||
features = append(features, "反向代理")
|
||||
}
|
||||
if hasRewrite {
|
||||
features = append(features, "URL重写")
|
||||
}
|
||||
if hasSSL {
|
||||
features = append(features, "SSL配置")
|
||||
}
|
||||
|
||||
// === RAG 增强:查询 Nginx 配置迁移最佳实践 ===
|
||||
var ragContext *rag.RAGContext
|
||||
if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() && len(features) > 0 {
|
||||
query := fmt.Sprintf("Nginx %s 迁移到 Higress 的配置方法和最佳实践", strings.Join(features, "、"))
|
||||
var err error
|
||||
ragContext, err = ctx.RAGManager.QueryForTool("parse_nginx_config", query, "nginx_migration")
|
||||
if err != nil {
|
||||
log.Printf(" RAG query failed for parse_nginx_config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 构建分析结果
|
||||
var result strings.Builder
|
||||
|
||||
// RAG 上下文(如果有)
|
||||
if ragContext != nil && ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
result.WriteString("📚 知识库迁移指南:\n\n")
|
||||
result.WriteString(ragContext.FormatContextForAI())
|
||||
result.WriteString("\n---\n\n")
|
||||
}
|
||||
|
||||
result.WriteString(fmt.Sprintf(`Nginx配置分析结果
|
||||
|
||||
基础信息:
|
||||
- Server块: %d个
|
||||
- Location块: %d个
|
||||
- SSL配置: %t
|
||||
- 反向代理: %t
|
||||
- URL重写: %t
|
||||
|
||||
复杂度: %s
|
||||
|
||||
迁移建议:`, serverCount, locationCount, hasSSL, hasProxy, hasRewrite, complexity))
|
||||
|
||||
if hasProxy {
|
||||
result.WriteString("\n- 反向代理将转换为Ingress backend配置")
|
||||
}
|
||||
if hasRewrite {
|
||||
result.WriteString("\n- URL重写将使用Higress注解 (higress.io/rewrite-target)")
|
||||
}
|
||||
if hasSSL {
|
||||
result.WriteString("\n- SSL配置将转换为Ingress TLS配置")
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
func convertToHigress(args map[string]interface{}, ctx *MigrationContext) (string, error) {
|
||||
configContent, ok := args["config_content"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid config_content parameter")
|
||||
}
|
||||
|
||||
namespace := ctx.DefaultNamespace
|
||||
if ns, ok := args["namespace"].(string); ok && ns != "" {
|
||||
namespace = ns
|
||||
}
|
||||
|
||||
// 检查是否使用 Gateway API
|
||||
useGatewayAPI := false
|
||||
if val, ok := args["use_gateway_api"].(bool); ok {
|
||||
useGatewayAPI = val
|
||||
}
|
||||
|
||||
// === 使用增强的解析器解析 Nginx 配置 ===
|
||||
nginxConfig, err := tools.ParseNginxConfig(configContent)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse Nginx config: %v", err)
|
||||
}
|
||||
|
||||
// 分析配置
|
||||
analysis := tools.AnalyzeNginxConfig(nginxConfig)
|
||||
|
||||
// === RAG 增强:查询转换示例和最佳实践 ===
|
||||
var ragContext string
|
||||
if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() {
|
||||
// 构建查询关键词
|
||||
queryBuilder := []string{"Nginx 配置转换到 Higress"}
|
||||
|
||||
if useGatewayAPI {
|
||||
queryBuilder = append(queryBuilder, "Gateway API HTTPRoute")
|
||||
} else {
|
||||
queryBuilder = append(queryBuilder, "Kubernetes Ingress")
|
||||
}
|
||||
|
||||
// 根据特性添加查询关键词
|
||||
if analysis.Features["ssl"] {
|
||||
queryBuilder = append(queryBuilder, "SSL TLS 证书配置")
|
||||
}
|
||||
if analysis.Features["rewrite"] {
|
||||
queryBuilder = append(queryBuilder, "URL 重写 rewrite 规则")
|
||||
}
|
||||
if analysis.Features["redirect"] {
|
||||
queryBuilder = append(queryBuilder, "重定向 redirect")
|
||||
}
|
||||
if analysis.Features["header_manipulation"] {
|
||||
queryBuilder = append(queryBuilder, "请求头 响应头处理")
|
||||
}
|
||||
if len(nginxConfig.Upstreams) > 0 {
|
||||
queryBuilder = append(queryBuilder, "负载均衡 upstream")
|
||||
}
|
||||
|
||||
queryString := strings.Join(queryBuilder, " ")
|
||||
log.Printf("🔍 RAG Query: %s", queryString)
|
||||
|
||||
ragResult, err := ctx.RAGManager.QueryForTool(
|
||||
"convert_to_higress",
|
||||
queryString,
|
||||
"nginx_to_higress",
|
||||
)
|
||||
|
||||
if err == nil && ragResult.Enabled && len(ragResult.Documents) > 0 {
|
||||
log.Printf("✅ RAG: Found %d documents for conversion", len(ragResult.Documents))
|
||||
ragContext = "\n\n## 📚 参考文档(来自知识库)\n\n" + ragResult.FormatContextForAI()
|
||||
} else {
|
||||
if err != nil {
|
||||
log.Printf("⚠️ RAG query failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 将配置数据转换为 JSON 供 AI 使用 ===
|
||||
configJSON, _ := json.MarshalIndent(nginxConfig, "", " ")
|
||||
analysisJSON, _ := json.MarshalIndent(analysis, "", " ")
|
||||
|
||||
// === 构建返回消息 ===
|
||||
var result strings.Builder
|
||||
|
||||
result.WriteString(fmt.Sprintf(`📋 Nginx 配置解析完成
|
||||
|
||||
## 配置概览
|
||||
- Server 块: %d
|
||||
- Location 块: %d
|
||||
- 域名: %d 个
|
||||
- 复杂度: %s
|
||||
- 目标格式: %s
|
||||
- 命名空间: %s
|
||||
|
||||
## 检测到的特性
|
||||
%s
|
||||
|
||||
## 迁移建议
|
||||
%s
|
||||
%s
|
||||
|
||||
---
|
||||
|
||||
## Nginx 配置结构
|
||||
|
||||
`+"```json"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
## 分析结果
|
||||
|
||||
`+"```json"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
%s
|
||||
`,
|
||||
analysis.ServerCount,
|
||||
analysis.LocationCount,
|
||||
analysis.DomainCount,
|
||||
analysis.Complexity,
|
||||
func() string {
|
||||
if useGatewayAPI {
|
||||
return "Gateway API (HTTPRoute)"
|
||||
}
|
||||
return "Kubernetes Ingress"
|
||||
}(),
|
||||
namespace,
|
||||
formatFeaturesForOutput(analysis.Features),
|
||||
formatSuggestionsForOutput(analysis.Suggestions),
|
||||
func() string {
|
||||
if ragContext != "" {
|
||||
return "\n\n✅ 已加载知识库参考文档"
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
string(configJSON),
|
||||
string(analysisJSON),
|
||||
ragContext,
|
||||
))
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
// generateIngressConfig 生成 Ingress 资源配置(主要方式)
|
||||
func generateIngressConfig(ingressName, namespace, hostname, serviceName string, ctx *MigrationContext) string {
|
||||
return fmt.Sprintf(`转换后的Higress配置(使用 Ingress - 推荐方式)
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: %s
|
||||
namespace: %s
|
||||
annotations:
|
||||
higress.io/migrated-from: "nginx"
|
||||
higress.io/ingress.class: "higress"
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: %s
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: %s
|
||||
port:
|
||||
number: %d
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: %s
|
||||
namespace: %s
|
||||
spec:
|
||||
selector:
|
||||
app: backend
|
||||
ports:
|
||||
- port: %d
|
||||
targetPort: %d
|
||||
protocol: TCP
|
||||
|
||||
转换完成
|
||||
|
||||
应用步骤:
|
||||
1. 保存为 higress-config.yaml
|
||||
2. 执行: kubectl apply -f higress-config.yaml
|
||||
3. 验证: kubectl get ingress -n %s
|
||||
|
||||
说明:
|
||||
- 使用 Ingress 是 Higress 的主要使用方式,兼容性最好
|
||||
- 如需使用 Gateway API (HTTPRoute),请设置参数 use_gateway_api=true`,
|
||||
ingressName, namespace,
|
||||
hostname,
|
||||
serviceName, ctx.ServicePort,
|
||||
serviceName, namespace,
|
||||
ctx.ServicePort, ctx.TargetPort,
|
||||
namespace)
|
||||
}
|
||||
|
||||
// generateHTTPRouteConfig 生成 HTTPRoute 资源配置(备用选项)
|
||||
func generateHTTPRouteConfig(routeName, namespace, hostname, serviceName string, ctx *MigrationContext) string {
|
||||
return fmt.Sprintf(`转换后的Higress配置(使用 Gateway API - 可选方式)
|
||||
|
||||
注意: Gateway API 在 Higress 中默认关闭,使用前需要确认已启用。
|
||||
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: %s
|
||||
namespace: %s
|
||||
annotations:
|
||||
higress.io/migrated-from: "nginx"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: %s
|
||||
namespace: %s
|
||||
hostnames:
|
||||
- %s
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: %s
|
||||
port: %d
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: %s
|
||||
namespace: %s
|
||||
spec:
|
||||
selector:
|
||||
app: backend
|
||||
ports:
|
||||
- port: %d
|
||||
targetPort: %d
|
||||
protocol: TCP
|
||||
|
||||
转换完成
|
||||
|
||||
应用步骤:
|
||||
1. 确认 Gateway API 已启用: PILOT_ENABLE_GATEWAY_API=true
|
||||
2. 保存为 higress-config.yaml
|
||||
3. 执行: kubectl apply -f higress-config.yaml
|
||||
4. 验证: kubectl get httproute -n %s
|
||||
|
||||
说明:
|
||||
- Gateway API 是可选功能,默认关闭
|
||||
- 推荐使用 Ingress (设置 use_gateway_api=false)`,
|
||||
routeName, namespace,
|
||||
ctx.GatewayName, ctx.GatewayNamespace, hostname,
|
||||
serviceName, ctx.ServicePort,
|
||||
serviceName, namespace,
|
||||
ctx.ServicePort, ctx.TargetPort,
|
||||
namespace)
|
||||
}
|
||||
|
||||
func generateIngressName(hostname string, ctx *MigrationContext) string {
|
||||
prefix := "nginx-migrated"
|
||||
if ctx.RoutePrefix != "" {
|
||||
prefix = ctx.RoutePrefix
|
||||
}
|
||||
|
||||
if hostname == "" || hostname == ctx.DefaultHostname {
|
||||
return fmt.Sprintf("%s-ingress", prefix)
|
||||
}
|
||||
// Replace dots and special characters for valid k8s name
|
||||
safeName := hostname
|
||||
for _, char := range []string{".", "_", ":"} {
|
||||
safeName = strings.ReplaceAll(safeName, char, "-")
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", prefix, safeName)
|
||||
}
|
||||
|
||||
func generateRouteName(hostname string, ctx *MigrationContext) string {
|
||||
prefix := "nginx-migrated"
|
||||
if ctx.RoutePrefix != "" {
|
||||
prefix = ctx.RoutePrefix
|
||||
}
|
||||
|
||||
if hostname == "" || hostname == ctx.DefaultHostname {
|
||||
return fmt.Sprintf("%s-route", prefix)
|
||||
}
|
||||
// Replace dots and special characters for valid k8s name
|
||||
safeName := hostname
|
||||
for _, char := range []string{".", "_", ":"} {
|
||||
safeName = strings.ReplaceAll(safeName, char, "-")
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", prefix, safeName)
|
||||
}
|
||||
|
||||
func generateServiceName(hostname string, ctx *MigrationContext) string {
|
||||
if hostname == "" || hostname == ctx.DefaultHostname {
|
||||
return "backend-service"
|
||||
}
|
||||
safeName := hostname
|
||||
for _, char := range []string{".", "_", ":"} {
|
||||
safeName = strings.ReplaceAll(safeName, char, "-")
|
||||
}
|
||||
return fmt.Sprintf("%s-service", safeName)
|
||||
}
|
||||
|
||||
// formatFeaturesForOutput 格式化特性列表用于输出
|
||||
func formatFeaturesForOutput(features map[string]bool) string {
|
||||
featureNames := map[string]string{
|
||||
"ssl": "SSL/TLS 加密",
|
||||
"proxy": "反向代理",
|
||||
"rewrite": "URL 重写",
|
||||
"redirect": "重定向",
|
||||
"return": "返回指令",
|
||||
"complex_routing": "复杂路由匹配",
|
||||
"header_manipulation": "请求头操作",
|
||||
"response_headers": "响应头操作",
|
||||
}
|
||||
|
||||
var result []string
|
||||
for key, enabled := range features {
|
||||
if enabled {
|
||||
if name, ok := featureNames[key]; ok {
|
||||
result = append(result, fmt.Sprintf("- ✅ %s", name))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("- ✅ %s", key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return "- 基础配置(无特殊特性)"
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
// formatSuggestionsForOutput 格式化建议列表用于输出
|
||||
func formatSuggestionsForOutput(suggestions []string) string {
|
||||
if len(suggestions) == 0 {
|
||||
return "- 无特殊建议"
|
||||
}
|
||||
var result []string
|
||||
for _, s := range suggestions {
|
||||
result = append(result, fmt.Sprintf("- 💡 %s", s))
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
// Package mcptools 提供 RAG 集成到 MCP 工具的示例实现
|
||||
package mcptools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"nginx-migration-mcp/internal/rag"
|
||||
)
|
||||
|
||||
// RAGToolContext MCP 工具的 RAG 上下文
|
||||
type RAGToolContext struct {
|
||||
Manager *rag.RAGManager
|
||||
}
|
||||
|
||||
// NewRAGToolContext 创建 RAG 工具上下文
|
||||
func NewRAGToolContext(configPath string) (*RAGToolContext, error) {
|
||||
// 加载配置
|
||||
config, err := rag.LoadRAGConfig(configPath)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ Failed to load RAG config: %v, RAG will be disabled", err)
|
||||
// 创建禁用状态的配置
|
||||
config = &rag.RAGConfig{Enabled: false}
|
||||
}
|
||||
|
||||
// 创建 RAG 管理器
|
||||
manager := rag.NewRAGManager(config)
|
||||
|
||||
return &RAGToolContext{
|
||||
Manager: manager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ==================== 工具示例:generate_conversion_hints ====================
|
||||
|
||||
// GenerateConversionHintsWithRAG 生成转换提示(带 RAG 增强)
|
||||
func (ctx *RAGToolContext) GenerateConversionHintsWithRAG(analysisResult string, pluginName string) (string, error) {
|
||||
var result strings.Builder
|
||||
result.WriteString(fmt.Sprintf("# %s 插件转换指南\n\n", pluginName))
|
||||
|
||||
// 提取 Nginx APIs(这里简化处理)
|
||||
nginxAPIs := extractNginxAPIs(analysisResult)
|
||||
|
||||
// === 核心:使用工具级别的 RAG 查询 ===
|
||||
toolName := "generate_conversion_hints"
|
||||
ragContext, err := ctx.Manager.QueryForTool(
|
||||
toolName,
|
||||
fmt.Sprintf("Nginx Lua API %s 在 Higress WASM 中的实现和转换方法", strings.Join(nginxAPIs, ", ")),
|
||||
"lua_migration",
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("❌ RAG query failed for %s: %v", toolName, err)
|
||||
// 降级到规则生成
|
||||
ragContext = &rag.RAGContext{
|
||||
Enabled: false,
|
||||
Message: fmt.Sprintf("RAG query failed: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 RAG 上下文信息(如果有)
|
||||
if ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
result.WriteString(ragContext.FormatContextForAI())
|
||||
} else {
|
||||
// RAG 未启用或查询失败
|
||||
result.WriteString(fmt.Sprintf("> ℹ️ %s\n\n", ragContext.Message))
|
||||
result.WriteString("> 使用基于规则的转换指南\n\n")
|
||||
}
|
||||
|
||||
// 为每个 API 生成转换提示(基于规则)
|
||||
result.WriteString("## 🔄 API 转换详情\n\n")
|
||||
for _, api := range nginxAPIs {
|
||||
result.WriteString(fmt.Sprintf("### %s\n\n", api))
|
||||
result.WriteString(generateBasicMapping(api))
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// 添加使用建议
|
||||
result.WriteString("\n---\n\n")
|
||||
result.WriteString("## 💡 使用建议\n\n")
|
||||
if ragContext.Enabled {
|
||||
result.WriteString("✅ 上述参考文档来自 Higress 官方知识库,请参考这些文档中的示例代码和最佳实践来生成 WASM 插件代码。\n\n")
|
||||
result.WriteString("建议按照知识库中的示例实现,确保代码符合 Higress 的最佳实践。\n")
|
||||
} else {
|
||||
result.WriteString("ℹ️ 当前未启用 RAG 知识库或查询失败,使用基于规则的映射。\n\n")
|
||||
result.WriteString("建议参考 Higress 官方文档:https://higress.cn/docs/plugins/wasm-go-sdk/\n")
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
// ==================== 工具示例:validate_wasm_code ====================
|
||||
|
||||
// ValidateWasmCodeWithRAG 验证 WASM 代码(带 RAG 增强)
|
||||
func (ctx *RAGToolContext) ValidateWasmCodeWithRAG(goCode string, pluginName string) (string, error) {
|
||||
var result strings.Builder
|
||||
result.WriteString(fmt.Sprintf("## 🔍 %s 插件代码验证报告\n\n", pluginName))
|
||||
|
||||
// 基本验证(始终执行)
|
||||
basicIssues := validateBasicSyntax(goCode)
|
||||
apiIssues := validateAPIUsage(goCode)
|
||||
|
||||
if len(basicIssues) > 0 {
|
||||
result.WriteString("### ⚠️ 语法问题\n\n")
|
||||
for _, issue := range basicIssues {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", issue))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
if len(apiIssues) > 0 {
|
||||
result.WriteString("### ⚠️ API 使用问题\n\n")
|
||||
for _, issue := range apiIssues {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", issue))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// === RAG 增强:查询最佳实践 ===
|
||||
toolName := "validate_wasm_code"
|
||||
ragContext, err := ctx.Manager.QueryForTool(
|
||||
toolName,
|
||||
"Higress WASM 插件开发最佳实践 错误处理 性能优化 代码规范",
|
||||
"best_practice",
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("❌ RAG query failed for %s: %v", toolName, err)
|
||||
ragContext = &rag.RAGContext{
|
||||
Enabled: false,
|
||||
Message: fmt.Sprintf("RAG query failed: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
// 添加最佳实践建议
|
||||
if ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
result.WriteString("### 💡 最佳实践建议(基于知识库)\n\n")
|
||||
|
||||
for i, doc := range ragContext.Documents {
|
||||
result.WriteString(fmt.Sprintf("#### 建议 %d:%s\n\n", i+1, doc.Title))
|
||||
result.WriteString(fmt.Sprintf("**来源**: %s \n", doc.Source))
|
||||
if doc.URL != "" {
|
||||
result.WriteString(fmt.Sprintf("**链接**: %s \n", doc.URL))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
|
||||
// 只展示关键片段(validate 工具通常配置为 highlights 模式)
|
||||
if len(doc.Highlights) > 0 {
|
||||
result.WriteString("**关键要点**:\n\n")
|
||||
for _, h := range doc.Highlights {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", h))
|
||||
}
|
||||
} else {
|
||||
result.WriteString("**参考内容**:\n\n")
|
||||
result.WriteString("```\n")
|
||||
result.WriteString(doc.Content)
|
||||
result.WriteString("\n```\n")
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// 基于知识库内容检查当前代码
|
||||
suggestions := checkCodeAgainstBestPractices(goCode, ragContext.Documents)
|
||||
if len(suggestions) > 0 {
|
||||
result.WriteString("### 📝 针对当前代码的改进建议\n\n")
|
||||
for _, s := range suggestions {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", s))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
} else {
|
||||
result.WriteString("### 💡 基本建议\n\n")
|
||||
result.WriteString(fmt.Sprintf("> %s\n\n", ragContext.Message))
|
||||
result.WriteString(generateBasicValidationSuggestions(goCode))
|
||||
}
|
||||
|
||||
// 验证总结
|
||||
if len(basicIssues) == 0 && len(apiIssues) == 0 {
|
||||
result.WriteString("\n---\n\n")
|
||||
result.WriteString("### ✅ 验证通过\n\n")
|
||||
result.WriteString("代码基本验证通过,没有发现明显问题。\n")
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
// ==================== 工具示例:convert_lua_to_wasm ====================
|
||||
|
||||
// ConvertLuaToWasmWithRAG 快速转换(通常不使用 RAG)
|
||||
func (ctx *RAGToolContext) ConvertLuaToWasmWithRAG(luaCode string, pluginName string) (string, error) {
|
||||
// 这个工具在配置中通常设置为 use_rag: false,保持快速响应
|
||||
|
||||
toolName := "convert_lua_to_wasm"
|
||||
|
||||
// 仍然可以查询,但如果配置禁用则会快速返回
|
||||
ragContext, _ := ctx.Manager.QueryForTool(
|
||||
toolName,
|
||||
"Lua to WASM conversion examples",
|
||||
"quick_convert",
|
||||
)
|
||||
|
||||
var result strings.Builder
|
||||
result.WriteString(fmt.Sprintf("# %s 插件转换结果\n\n", pluginName))
|
||||
|
||||
if ragContext.Enabled {
|
||||
result.WriteString("> 🚀 使用 RAG 增强转换\n\n")
|
||||
result.WriteString(ragContext.FormatContextForAI())
|
||||
} else {
|
||||
result.WriteString("> ⚡ 快速转换模式(未启用 RAG)\n\n")
|
||||
}
|
||||
|
||||
// 执行基于规则的转换
|
||||
wasmCode := performRuleBasedConversion(luaCode, pluginName)
|
||||
result.WriteString("## 生成的 Go WASM 代码\n\n")
|
||||
result.WriteString("```go\n")
|
||||
result.WriteString(wasmCode)
|
||||
result.WriteString("\n```\n")
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
func extractNginxAPIs(analysisResult string) []string {
|
||||
// 简化实现:从分析结果中提取 API
|
||||
apis := []string{"ngx.req.get_headers", "ngx.say", "ngx.var"}
|
||||
return apis
|
||||
}
|
||||
|
||||
func generateBasicMapping(api string) string {
|
||||
mappings := map[string]string{
|
||||
"ngx.req.get_headers": "**Higress WASM**: `proxywasm.GetHttpRequestHeaders()`\n\n示例:\n```go\nheaders, err := proxywasm.GetHttpRequestHeaders()\nif err != nil {\n proxywasm.LogError(\"failed to get headers\")\n return types.ActionContinue\n}\n```",
|
||||
"ngx.say": "**Higress WASM**: `proxywasm.SendHttpResponse()`",
|
||||
"ngx.var": "**Higress WASM**: `proxywasm.GetProperty()`",
|
||||
}
|
||||
|
||||
if mapping, ok := mappings[api]; ok {
|
||||
return mapping
|
||||
}
|
||||
return "映射信息暂未提供,请参考官方文档。"
|
||||
}
|
||||
|
||||
func validateBasicSyntax(goCode string) []string {
|
||||
// 简化实现
|
||||
issues := []string{}
|
||||
if !strings.Contains(goCode, "package main") {
|
||||
issues = append(issues, "缺少 package main 声明")
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
func validateAPIUsage(goCode string) []string {
|
||||
// 简化实现
|
||||
issues := []string{}
|
||||
if strings.Contains(goCode, "proxywasm.") && !strings.Contains(goCode, "import") {
|
||||
issues = append(issues, "使用了 proxywasm API 但未导入相关包")
|
||||
}
|
||||
return issues
|
||||
}
|
||||
|
||||
func checkCodeAgainstBestPractices(goCode string, docs []rag.ContextDocument) []string {
|
||||
// 简化实现:基于文档内容检查代码
|
||||
suggestions := []string{}
|
||||
|
||||
// 检查错误处理
|
||||
if !strings.Contains(goCode, "if err != nil") {
|
||||
for _, doc := range docs {
|
||||
if strings.Contains(doc.Content, "错误处理") || strings.Contains(doc.Content, "error handling") {
|
||||
suggestions = append(suggestions, "建议添加完善的错误处理逻辑(参考知识库文档)")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查日志记录
|
||||
if !strings.Contains(goCode, "proxywasm.Log") {
|
||||
suggestions = append(suggestions, "建议添加适当的日志记录以便调试")
|
||||
}
|
||||
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func generateBasicValidationSuggestions(goCode string) string {
|
||||
return "- 确保所有 API 调用都有错误处理\n" +
|
||||
"- 添加必要的日志记录\n" +
|
||||
"- 遵循 Higress WASM 插件开发规范\n"
|
||||
}
|
||||
|
||||
func performRuleBasedConversion(luaCode string, pluginName string) string {
|
||||
// 简化实现:基于规则的转换
|
||||
return fmt.Sprintf(`package main
|
||||
|
||||
import (
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/higress-group/wasm-go/pkg/wrapper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
"%s",
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
|
||||
)
|
||||
}
|
||||
|
||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action {
|
||||
// TODO: 实现转换逻辑
|
||||
// 原始 Lua 代码:
|
||||
// %s
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
type PluginConfig struct {
|
||||
// TODO: 添加配置字段
|
||||
}
|
||||
`, pluginName, luaCode)
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
//go:build higress_integration
|
||||
// +build higress_integration
|
||||
|
||||
package mcptools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"nginx-migration-mcp/tools"
|
||||
|
||||
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
|
||||
)
|
||||
|
||||
// RegisterToolChainTools 注册工具链相关的工具
|
||||
func RegisterToolChainTools(server *common.MCPServer, ctx *MigrationContext) {
|
||||
RegisterSimpleTool(
|
||||
server,
|
||||
"generate_conversion_hints",
|
||||
"基于 Lua 分析结果生成代码转换模板",
|
||||
map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"analysis_result": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "analyze_lua_plugin 返回的 JSON 格式分析结果",
|
||||
},
|
||||
"plugin_name": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "目标插件名称(小写字母和连字符)",
|
||||
},
|
||||
},
|
||||
"required": []string{"analysis_result", "plugin_name"},
|
||||
},
|
||||
func(args map[string]interface{}) (string, error) {
|
||||
return generateConversionHints(args, ctx)
|
||||
},
|
||||
)
|
||||
|
||||
RegisterSimpleTool(
|
||||
server,
|
||||
"validate_wasm_code",
|
||||
"验证生成的 Go WASM 插件代码,检查语法、API 使用和配置结构",
|
||||
map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"go_code": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "生成的 Go WASM 插件代码",
|
||||
},
|
||||
"plugin_name": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "插件名称",
|
||||
},
|
||||
},
|
||||
"required": []string{"go_code", "plugin_name"},
|
||||
},
|
||||
func(args map[string]interface{}) (string, error) {
|
||||
return validateWasmCode(args, ctx)
|
||||
},
|
||||
)
|
||||
|
||||
RegisterSimpleTool(
|
||||
server,
|
||||
"generate_deployment_config",
|
||||
"为验证通过的 WASM 插件生成完整的部署配置包",
|
||||
map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"plugin_name": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "插件名称",
|
||||
},
|
||||
"go_code": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "验证通过的 Go 代码",
|
||||
},
|
||||
"config_schema": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "配置 JSON Schema(可选)",
|
||||
},
|
||||
"namespace": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "部署命名空间",
|
||||
"default": "higress-system",
|
||||
},
|
||||
},
|
||||
"required": []string{"plugin_name", "go_code"},
|
||||
},
|
||||
func(args map[string]interface{}) (string, error) {
|
||||
return generateDeploymentConfig(args, ctx)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func generateConversionHints(args map[string]interface{}, ctx *MigrationContext) (string, error) {
|
||||
analysisResultStr, ok := args["analysis_result"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid analysis_result parameter")
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid plugin_name parameter")
|
||||
}
|
||||
|
||||
// 解析分析结果
|
||||
var analysis tools.AnalysisResultForAI
|
||||
if err := json.Unmarshal([]byte(analysisResultStr), &analysis); err != nil {
|
||||
return "", fmt.Errorf("failed to parse analysis_result: %w", err)
|
||||
}
|
||||
|
||||
// 生成转换提示
|
||||
hints := tools.GenerateConversionHints(analysis, pluginName)
|
||||
|
||||
// === RAG 增强(如果启用)===
|
||||
var ragInfo map[string]interface{}
|
||||
if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() {
|
||||
// 构建智能查询语句
|
||||
queryBuilder := []string{}
|
||||
if len(analysis.APICalls) > 0 {
|
||||
queryBuilder = append(queryBuilder, "Nginx Lua API 转换到 Higress WASM")
|
||||
|
||||
hasHeaderOps := analysis.Features["header_manipulation"] || analysis.Features["request_headers"] || analysis.Features["response_headers"]
|
||||
hasBodyOps := analysis.Features["request_body"] || analysis.Features["response_body"]
|
||||
hasResponseControl := analysis.Features["response_control"]
|
||||
|
||||
if hasHeaderOps {
|
||||
queryBuilder = append(queryBuilder, "请求头和响应头处理")
|
||||
}
|
||||
if hasBodyOps {
|
||||
queryBuilder = append(queryBuilder, "请求体和响应体处理")
|
||||
}
|
||||
if hasResponseControl {
|
||||
queryBuilder = append(queryBuilder, "响应控制和状态码设置")
|
||||
}
|
||||
|
||||
if len(analysis.APICalls) > 0 && len(analysis.APICalls) <= 5 {
|
||||
queryBuilder = append(queryBuilder, fmt.Sprintf("涉及 API: %s", strings.Join(analysis.APICalls, ", ")))
|
||||
}
|
||||
} else {
|
||||
queryBuilder = append(queryBuilder, "Higress WASM 插件开发 基础示例 Go SDK 使用")
|
||||
}
|
||||
|
||||
if analysis.Complexity == "high" {
|
||||
queryBuilder = append(queryBuilder, "复杂插件实现 高级功能")
|
||||
}
|
||||
|
||||
queryString := strings.Join(queryBuilder, " ")
|
||||
|
||||
ragContext, err := ctx.RAGManager.QueryForTool(
|
||||
"generate_conversion_hints",
|
||||
queryString,
|
||||
"lua_migration",
|
||||
)
|
||||
if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
ragInfo = map[string]interface{}{
|
||||
"enabled": true,
|
||||
"documents": len(ragContext.Documents),
|
||||
"context": ragContext.FormatContextForAI(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 组合结果
|
||||
result := map[string]interface{}{
|
||||
"code_template": hints.CodeTemplate,
|
||||
"warnings": hints.Warnings,
|
||||
"rag": ragInfo,
|
||||
}
|
||||
|
||||
// 返回 JSON 结果,由 LLM 解释和使用
|
||||
resultJSON, _ := json.MarshalIndent(result, "", " ")
|
||||
return string(resultJSON), nil
|
||||
}
|
||||
|
||||
func validateWasmCode(args map[string]interface{}, ctx *MigrationContext) (string, error) {
|
||||
goCode, ok := args["go_code"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid go_code parameter")
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid plugin_name parameter")
|
||||
}
|
||||
|
||||
// 执行验证(AI 驱动)
|
||||
report := tools.ValidateWasmCode(goCode, pluginName)
|
||||
|
||||
// 格式化输出,包含 AI 分析提示和基础信息
|
||||
var result strings.Builder
|
||||
|
||||
result.WriteString(fmt.Sprintf("## 代码验证报告\n\n"))
|
||||
result.WriteString(fmt.Sprintf("代码存在 %d 个必须修复的问题,%d 个建议修复的问题,%d 个可选优化项,%d 个最佳实践建议。请优先解决必须修复的问题。\n\n", 0, 0, 0, 0))
|
||||
|
||||
result.WriteString(fmt.Sprintf("### 发现的回调函数 (%d 个)\n", len(report.FoundCallbacks)))
|
||||
if len(report.FoundCallbacks) > 0 {
|
||||
for _, cb := range report.FoundCallbacks {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", cb))
|
||||
}
|
||||
} else {
|
||||
result.WriteString("无\n")
|
||||
}
|
||||
result.WriteString("\n")
|
||||
|
||||
result.WriteString("### 配置结构\n")
|
||||
if report.HasConfig {
|
||||
result.WriteString(" 已定义配置结构体\n\n")
|
||||
} else {
|
||||
result.WriteString(" 未定义配置结构体\n\n")
|
||||
}
|
||||
|
||||
result.WriteString("### 问题分类\n\n")
|
||||
|
||||
result.WriteString("#### 必须修复 (0 个)\n")
|
||||
result.WriteString("无\n\n")
|
||||
|
||||
result.WriteString("#### 建议修复 (0 个)\n")
|
||||
result.WriteString("无\n\n")
|
||||
|
||||
result.WriteString("#### 可选优化 (0 个)\n")
|
||||
result.WriteString("无\n\n")
|
||||
|
||||
result.WriteString("#### 最佳实践 (0 个)\n")
|
||||
result.WriteString("无\n\n")
|
||||
|
||||
// 添加 AI 分析提示
|
||||
result.WriteString("---\n\n")
|
||||
result.WriteString(report.Summary)
|
||||
result.WriteString("\n\n")
|
||||
|
||||
// === RAG 增强:查询最佳实践 ===
|
||||
if ctx.RAGManager != nil && ctx.RAGManager.IsEnabled() {
|
||||
// 构建智能查询语句
|
||||
queryBuilder := []string{"Higress WASM 插件"}
|
||||
|
||||
// 根据回调函数类型添加特定查询
|
||||
for _, callback := range report.FoundCallbacks {
|
||||
if strings.Contains(callback, "RequestHeaders") {
|
||||
queryBuilder = append(queryBuilder, "请求头处理")
|
||||
}
|
||||
if strings.Contains(callback, "RequestBody") {
|
||||
queryBuilder = append(queryBuilder, "请求体处理")
|
||||
}
|
||||
if strings.Contains(callback, "ResponseHeaders") {
|
||||
queryBuilder = append(queryBuilder, "响应头处理")
|
||||
}
|
||||
}
|
||||
|
||||
queryBuilder = append(queryBuilder, "性能优化 最佳实践 错误处理")
|
||||
queryString := strings.Join(queryBuilder, " ")
|
||||
|
||||
ragContext, err := ctx.RAGManager.QueryForTool(
|
||||
"validate_wasm_code",
|
||||
queryString,
|
||||
"best_practice",
|
||||
)
|
||||
if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
result.WriteString("\n\n### 📚 最佳实践建议(来自知识库)\n\n")
|
||||
result.WriteString(ragContext.FormatContextForAI())
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 JSON 格式的结构化数据(供后续处理)
|
||||
reportJSON, _ := json.MarshalIndent(report, "", " ")
|
||||
result.WriteString("\n")
|
||||
result.WriteString(string(reportJSON))
|
||||
|
||||
return result.String(), nil
|
||||
}
|
||||
|
||||
func generateDeploymentConfig(args map[string]interface{}, ctx *MigrationContext) (string, error) {
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid plugin_name parameter")
|
||||
}
|
||||
|
||||
_, ok = args["go_code"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("missing or invalid go_code parameter")
|
||||
}
|
||||
|
||||
namespace := "higress-system"
|
||||
if ns, ok := args["namespace"].(string); ok && ns != "" {
|
||||
namespace = ns
|
||||
}
|
||||
|
||||
// configSchema is optional, we don't use it for now but don't return error
|
||||
_ = args["config_schema"]
|
||||
|
||||
// 返回提示信息,由 LLM 生成具体配置文件
|
||||
result := fmt.Sprintf(`为插件 %s 生成以下部署配置:
|
||||
1. WasmPlugin YAML (namespace: %s)
|
||||
2. Makefile (TinyGo 构建)
|
||||
3. Dockerfile
|
||||
4. README.md
|
||||
5. 测试脚本
|
||||
|
||||
参考文档: https://higress.cn/docs/latest/user/wasm-go/`, pluginName, namespace)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
//go:build higress_integration
|
||||
// +build higress_integration
|
||||
|
||||
package nginx_migration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"nginx-migration-mcp/integration/mcptools"
|
||||
"nginx-migration-mcp/internal/rag"
|
||||
|
||||
"github.com/alibaba/higress/plugins/golang-filter/mcp-session/common"
|
||||
"github.com/envoyproxy/envoy/contrib/golang/common/go/api"
|
||||
)
|
||||
|
||||
const Version = "1.0.0"
|
||||
|
||||
func init() {
|
||||
common.GlobalRegistry.RegisterServer("nginx-migration", &NginxMigrationConfig{})
|
||||
}
|
||||
|
||||
// NginxMigrationConfig holds configuration for the Nginx Migration MCP Server
|
||||
type NginxMigrationConfig struct {
|
||||
gatewayName string
|
||||
gatewayNamespace string
|
||||
defaultNamespace string
|
||||
defaultHostname string
|
||||
description string
|
||||
ragConfigPath string // RAG 配置文件路径
|
||||
}
|
||||
|
||||
// ParseConfig parses the configuration map for the Nginx Migration server
|
||||
func (c *NginxMigrationConfig) ParseConfig(config map[string]interface{}) error {
|
||||
// Optional configurations with defaults
|
||||
if gatewayName, ok := config["gatewayName"].(string); ok {
|
||||
c.gatewayName = gatewayName
|
||||
} else {
|
||||
c.gatewayName = "higress-gateway"
|
||||
}
|
||||
|
||||
if gatewayNamespace, ok := config["gatewayNamespace"].(string); ok {
|
||||
c.gatewayNamespace = gatewayNamespace
|
||||
} else {
|
||||
c.gatewayNamespace = "higress-system"
|
||||
}
|
||||
|
||||
if defaultNamespace, ok := config["defaultNamespace"].(string); ok {
|
||||
c.defaultNamespace = defaultNamespace
|
||||
} else {
|
||||
c.defaultNamespace = "default"
|
||||
}
|
||||
|
||||
if defaultHostname, ok := config["defaultHostname"].(string); ok {
|
||||
c.defaultHostname = defaultHostname
|
||||
} else {
|
||||
c.defaultHostname = "example.com"
|
||||
}
|
||||
|
||||
if desc, ok := config["description"].(string); ok {
|
||||
c.description = desc
|
||||
} else {
|
||||
c.description = "Nginx Migration MCP Server - Convert Nginx configs and Lua plugins to Higress"
|
||||
}
|
||||
|
||||
// RAG 配置路径(可选)
|
||||
if ragPath, ok := config["ragConfigPath"].(string); ok {
|
||||
c.ragConfigPath = ragPath
|
||||
} else {
|
||||
c.ragConfigPath = "config/rag.json" // 默认路径
|
||||
}
|
||||
|
||||
api.LogDebugf("NginxMigrationConfig ParseConfig: gatewayName=%s, gatewayNamespace=%s, defaultNamespace=%s, ragConfig=%s",
|
||||
c.gatewayName, c.gatewayNamespace, c.defaultNamespace, c.ragConfigPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewServer creates a new MCP server instance for Nginx Migration
|
||||
func (c *NginxMigrationConfig) NewServer(serverName string) (*common.MCPServer, error) {
|
||||
if serverName == "" {
|
||||
return nil, errors.New("server name cannot be empty")
|
||||
}
|
||||
|
||||
mcpServer := common.NewMCPServer(
|
||||
serverName,
|
||||
Version,
|
||||
common.WithInstructions("Nginx Migration MCP Server: Analyze and convert Nginx configurations and Lua plugins to Higress"),
|
||||
)
|
||||
|
||||
// Create migration context with configuration
|
||||
migrationCtx := &mcptools.MigrationContext{
|
||||
GatewayName: c.gatewayName,
|
||||
GatewayNamespace: c.gatewayNamespace,
|
||||
DefaultNamespace: c.defaultNamespace,
|
||||
DefaultHostname: c.defaultHostname,
|
||||
}
|
||||
|
||||
// 初始化 RAG Manager(如果配置了)
|
||||
if c.ragConfigPath != "" {
|
||||
api.LogInfof("Loading RAG config from: %s", c.ragConfigPath)
|
||||
ragConfig, err := rag.LoadRAGConfig(c.ragConfigPath)
|
||||
if err != nil {
|
||||
api.LogWarnf("Failed to load RAG config: %v, RAG will be disabled", err)
|
||||
// 不返回错误,继续使用无 RAG 的模式
|
||||
ragConfig = &rag.RAGConfig{Enabled: false}
|
||||
}
|
||||
|
||||
// 创建 RAG Manager
|
||||
migrationCtx.RAGManager = rag.NewRAGManager(ragConfig)
|
||||
|
||||
if migrationCtx.RAGManager.IsEnabled() {
|
||||
api.LogInfof("✅ RAG enabled for Nginx Migration MCP Server")
|
||||
} else {
|
||||
api.LogInfof("📖 RAG disabled, using rule-based approach")
|
||||
}
|
||||
}
|
||||
|
||||
// Register all migration tools
|
||||
mcptools.RegisterNginxConfigTools(mcpServer, migrationCtx)
|
||||
mcptools.RegisterLuaPluginTools(mcpServer, migrationCtx)
|
||||
mcptools.RegisterToolChainTools(mcpServer, migrationCtx)
|
||||
|
||||
api.LogInfof("Nginx Migration MCP Server initialized: %s (tools registered)", serverName)
|
||||
|
||||
return mcpServer, nil
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
// Package rag 提供基于阿里云官方 SDK 的 RAG 客户端实现
|
||||
package rag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
bailian "github.com/alibabacloud-go/bailian-20231229/v2/client"
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
// RAGQuery RAG 查询请求
|
||||
type RAGQuery struct {
|
||||
Query string `json:"query"` // 查询文本
|
||||
Scenario string `json:"scenario"` // 场景标识
|
||||
TopK int `json:"top_k"` // 返回文档数量
|
||||
ContextMode string `json:"context_mode"` // 上下文模式
|
||||
Filters map[string]interface{} `json:"filters"` // 过滤条件
|
||||
}
|
||||
|
||||
// RAGResponse RAG 查询响应
|
||||
type RAGResponse struct {
|
||||
Documents []RAGDocument `json:"documents"` // 检索到的文档
|
||||
Latency int64 `json:"latency"` // 查询延迟(毫秒)
|
||||
}
|
||||
|
||||
// RAGDocument 表示一个检索到的文档
|
||||
type RAGDocument struct {
|
||||
Title string `json:"title"` // 文档标题
|
||||
Content string `json:"content"` // 文档内容
|
||||
Source string `json:"source"` // 来源路径
|
||||
URL string `json:"url"` // 在线链接
|
||||
Score float64 `json:"score"` // 相关度分数
|
||||
Highlights []string `json:"highlights"` // 高亮片段
|
||||
}
|
||||
|
||||
// RAGClient 使用阿里云官方 SDK 的 RAG 客户端
|
||||
type RAGClient struct {
|
||||
config *RAGConfig
|
||||
client *bailian.Client
|
||||
cache *QueryCache
|
||||
}
|
||||
|
||||
// NewRAGClient 创建基于 SDK 的 RAG 客户端
|
||||
func NewRAGClient(config *RAGConfig) (*RAGClient, error) {
|
||||
// 创建 SDK 配置
|
||||
sdkConfig := &openapi.Config{
|
||||
AccessKeyId: tea.String(config.AccessKeyID),
|
||||
AccessKeySecret: tea.String(config.AccessKeySecret),
|
||||
}
|
||||
|
||||
// 设置端点(默认为北京区域)
|
||||
if config.Endpoint != "" {
|
||||
sdkConfig.Endpoint = tea.String(config.Endpoint)
|
||||
} else {
|
||||
sdkConfig.Endpoint = tea.String("bailian.cn-beijing.aliyuncs.com")
|
||||
}
|
||||
|
||||
// 创建客户端
|
||||
client, err := bailian.NewClient(sdkConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Bailian SDK client: %w", err)
|
||||
}
|
||||
|
||||
c := &RAGClient{
|
||||
config: config,
|
||||
client: client,
|
||||
}
|
||||
|
||||
// 初始化缓存
|
||||
if config.EnableCache {
|
||||
c.cache = NewQueryCache(config.CacheMaxSize, time.Duration(config.CacheTTL)*time.Second)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// SearchWithCache 查询知识库(带缓存)
|
||||
func (c *RAGClient) SearchWithCache(query *RAGQuery) (*RAGResponse, error) {
|
||||
// 检查缓存
|
||||
if c.cache != nil {
|
||||
cacheKey := c.buildCacheKey(query)
|
||||
if cached := c.cache.Get(cacheKey); cached != nil {
|
||||
if c.config.Debug {
|
||||
log.Printf("🎯 RAG cache hit: %s", query.Query)
|
||||
}
|
||||
return cached, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
startTime := time.Now()
|
||||
resp, err := c.search(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 记录延迟
|
||||
resp.Latency = time.Since(startTime).Milliseconds()
|
||||
|
||||
// 缓存结果
|
||||
if c.cache != nil {
|
||||
cacheKey := c.buildCacheKey(query)
|
||||
c.cache.Set(cacheKey, resp)
|
||||
}
|
||||
|
||||
if c.config.Debug {
|
||||
log.Printf("✅ RAG query completed: %s (latency: %dms, docs: %d)",
|
||||
query.Query, resp.Latency, len(resp.Documents))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// search 执行实际的查询(带重试)
|
||||
func (c *RAGClient) search(query *RAGQuery) (*RAGResponse, error) {
|
||||
var lastErr error
|
||||
|
||||
for attempt := 0; attempt <= c.config.MaxRetries; attempt++ {
|
||||
if attempt > 0 {
|
||||
// 重试前等待
|
||||
time.Sleep(time.Duration(c.config.RetryDelay) * time.Second)
|
||||
log.Printf("🔄 Retrying RAG query (attempt %d/%d)", attempt, c.config.MaxRetries)
|
||||
}
|
||||
|
||||
resp, err := c.doSearchSDK(query)
|
||||
if err == nil {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("RAG query failed after %d retries: %w", c.config.MaxRetries, lastErr)
|
||||
}
|
||||
|
||||
// doSearchSDK 执行单次查询(使用 SDK)
|
||||
func (c *RAGClient) doSearchSDK(query *RAGQuery) (*RAGResponse, error) {
|
||||
// 构建检索请求
|
||||
request := &bailian.RetrieveRequest{
|
||||
IndexId: tea.String(c.config.KnowledgeBaseID),
|
||||
Query: tea.String(query.Query),
|
||||
}
|
||||
|
||||
// 设置可选参数
|
||||
if query.TopK > 0 {
|
||||
request.DenseSimilarityTopK = tea.Int32(int32(query.TopK))
|
||||
} else {
|
||||
request.DenseSimilarityTopK = tea.Int32(int32(c.config.DefaultTopK))
|
||||
}
|
||||
|
||||
// 启用重排序
|
||||
request.EnableReranking = tea.Bool(true)
|
||||
|
||||
// 准备请求头和运行时选项
|
||||
headers := make(map[string]*string)
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
// 调用 SDK 检索接口
|
||||
response, err := c.client.RetrieveWithOptions(
|
||||
tea.String(c.config.WorkspaceID),
|
||||
request,
|
||||
headers,
|
||||
runtime,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SDK retrieve failed: %w", err)
|
||||
}
|
||||
|
||||
// 检查响应
|
||||
if response == nil || response.Body == nil {
|
||||
return nil, fmt.Errorf("empty response from SDK")
|
||||
}
|
||||
|
||||
if !tea.BoolValue(response.Body.Success) {
|
||||
return nil, fmt.Errorf("SDK returned Success=false, Code=%s, Message=%s",
|
||||
tea.StringValue(response.Body.Code),
|
||||
tea.StringValue(response.Body.Message))
|
||||
}
|
||||
|
||||
// 转换为 RAGResponse
|
||||
ragResp := &RAGResponse{
|
||||
Documents: make([]RAGDocument, 0),
|
||||
}
|
||||
|
||||
if response.Body.Data != nil && response.Body.Data.Nodes != nil {
|
||||
for _, node := range response.Body.Data.Nodes {
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 过滤低相关度文档
|
||||
score := tea.Float64Value(node.Score)
|
||||
if score < c.config.SimilarityThreshold {
|
||||
continue
|
||||
}
|
||||
|
||||
// 从 Metadata 中提取信息
|
||||
title := ""
|
||||
source := ""
|
||||
url := ""
|
||||
|
||||
if node.Metadata != nil {
|
||||
// Metadata 是 interface{} 类型,需要先转换为 map
|
||||
if meta, ok := node.Metadata.(map[string]interface{}); ok {
|
||||
if t, ok := meta["title"].(string); ok {
|
||||
title = t
|
||||
}
|
||||
if s, ok := meta["doc_name"].(string); ok {
|
||||
source = s
|
||||
}
|
||||
if u, ok := meta["file_path"].(string); ok {
|
||||
url = u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ragResp.Documents = append(ragResp.Documents, RAGDocument{
|
||||
Title: title,
|
||||
Content: tea.StringValue(node.Text),
|
||||
Source: source,
|
||||
URL: url,
|
||||
Score: score,
|
||||
Highlights: []string{}, // SDK 不返回 highlights
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ragResp, nil
|
||||
}
|
||||
|
||||
// buildCacheKey 构建缓存键
|
||||
func (c *RAGClient) buildCacheKey(query *RAGQuery) string {
|
||||
return fmt.Sprintf("%s:%s:top%d:%s", query.Scenario, query.Query, query.TopK, query.ContextMode)
|
||||
}
|
||||
|
||||
// QueryCache 查询缓存
|
||||
type QueryCache struct {
|
||||
entries map[string]*CacheEntry
|
||||
mu sync.RWMutex
|
||||
maxSize int
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// CacheEntry 缓存条目
|
||||
type CacheEntry struct {
|
||||
Response *RAGResponse
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// NewQueryCache 创建查询缓存
|
||||
func NewQueryCache(maxSize int, ttl time.Duration) *QueryCache {
|
||||
cache := &QueryCache{
|
||||
entries: make(map[string]*CacheEntry),
|
||||
maxSize: maxSize,
|
||||
ttl: ttl,
|
||||
}
|
||||
|
||||
// 启动清理协程
|
||||
go cache.cleanupLoop()
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// Get 获取缓存
|
||||
func (c *QueryCache) Get(key string) *RAGResponse {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
entry, ok := c.entries[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if time.Now().After(entry.ExpiresAt) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return entry.Response
|
||||
}
|
||||
|
||||
// Set 设置缓存
|
||||
func (c *QueryCache) Set(key string, resp *RAGResponse) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// 检查缓存大小
|
||||
if len(c.entries) >= c.maxSize {
|
||||
// 简单的 LRU:删除第一个条目
|
||||
for k := range c.entries {
|
||||
delete(c.entries, k)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.entries[key] = &CacheEntry{
|
||||
Response: resp,
|
||||
ExpiresAt: time.Now().Add(c.ttl),
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupLoop 清理过期缓存
|
||||
func (c *QueryCache) cleanupLoop() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
c.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup 执行清理
|
||||
func (c *QueryCache) cleanup() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for key, entry := range c.entries {
|
||||
if now.After(entry.ExpiresAt) {
|
||||
delete(c.entries, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Package rag 提供 RAG(检索增强生成)配置管理
|
||||
package rag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RAGConfig RAG 完整配置
|
||||
type RAGConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"enabled"` // RAG 功能总开关
|
||||
|
||||
// API 配置
|
||||
Provider string `json:"provider" yaml:"provider"` // 服务提供商: "bailian"
|
||||
Endpoint string `json:"endpoint" yaml:"endpoint"` // API 端点(如 bailian.cn-beijing.aliyuncs.com)
|
||||
WorkspaceID string `json:"workspace_id" yaml:"workspace_id"` // 业务空间 ID(百炼必需)
|
||||
AccessKeyID string `json:"access_key_id" yaml:"access_key_id"` // 阿里云 AccessKey ID
|
||||
AccessKeySecret string `json:"access_key_secret" yaml:"access_key_secret"` // 阿里云 AccessKey Secret
|
||||
KnowledgeBaseID string `json:"knowledge_base_id" yaml:"knowledge_base_id"` // 知识库 ID(IndexId)
|
||||
|
||||
// 上下文配置
|
||||
ContextMode string `json:"context_mode" yaml:"context_mode"` // full | summary | highlights
|
||||
MaxContextLength int `json:"max_context_length" yaml:"max_context_length"` // 最大上下文长度(字符数)
|
||||
|
||||
// 检索配置
|
||||
DefaultTopK int `json:"default_top_k" yaml:"default_top_k"` // 默认返回文档数量
|
||||
SimilarityThreshold float64 `json:"similarity_threshold" yaml:"similarity_threshold"` // 相似度阈值
|
||||
|
||||
// 缓存配置
|
||||
EnableCache bool `json:"enable_cache" yaml:"enable_cache"` // 是否启用缓存
|
||||
CacheTTL int `json:"cache_ttl" yaml:"cache_ttl"` // 缓存过期时间(秒)
|
||||
CacheMaxSize int `json:"cache_max_size" yaml:"cache_max_size"` // 最大缓存条目数
|
||||
|
||||
// 性能配置
|
||||
Timeout int `json:"timeout" yaml:"timeout"` // 请求超时时间(秒)
|
||||
MaxRetries int `json:"max_retries" yaml:"max_retries"` // 最大重试次数
|
||||
RetryDelay int `json:"retry_delay" yaml:"retry_delay"` // 重试间隔(秒)
|
||||
|
||||
// 降级策略
|
||||
FallbackOnError bool `json:"fallback_on_error" yaml:"fallback_on_error"` // RAG 失败时是否降级
|
||||
|
||||
// 工具级别配置(核心功能)
|
||||
Tools map[string]*ToolConfig `json:"tools" yaml:"tools"`
|
||||
|
||||
// 调试模式
|
||||
Debug bool `json:"debug" yaml:"debug"` // 是否启用调试日志
|
||||
LogQueries bool `json:"log_queries" yaml:"log_queries"` // 是否记录所有查询
|
||||
}
|
||||
|
||||
// ToolConfig 工具级别的 RAG 配置
|
||||
type ToolConfig struct {
|
||||
UseRAG bool `json:"use_rag" yaml:"use_rag"` // 是否使用 RAG
|
||||
ContextMode string `json:"context_mode" yaml:"context_mode"` // 上下文模式(覆盖全局配置)
|
||||
TopK int `json:"top_k" yaml:"top_k"` // 返回文档数量(覆盖全局配置)
|
||||
}
|
||||
|
||||
// LoadRAGConfig 从配置文件加载 RAG 配置
|
||||
// 注意:需要安装 YAML 库支持
|
||||
// 运行:go get gopkg.in/yaml.v3
|
||||
//
|
||||
// 临时实现:使用 JSON 格式配置文件
|
||||
func LoadRAGConfig(configPath string) (*RAGConfig, error) {
|
||||
// 读取配置文件
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config file: %w", err)
|
||||
}
|
||||
|
||||
// 简单的 YAML 到 JSON 转换(仅支持基本格式)
|
||||
// 在生产环境中应使用真正的 YAML 解析器
|
||||
jsonData := simpleYAMLToJSON(string(data))
|
||||
|
||||
// 解析 JSON
|
||||
var wrapper struct {
|
||||
RAG *RAGConfig `json:"rag"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(jsonData), &wrapper); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse config: %w", err)
|
||||
}
|
||||
|
||||
if wrapper.RAG == nil {
|
||||
return nil, fmt.Errorf("missing 'rag' section in config")
|
||||
}
|
||||
|
||||
config := wrapper.RAG
|
||||
|
||||
// 展开环境变量
|
||||
config.AccessKeyID = expandEnvVar(config.AccessKeyID)
|
||||
config.AccessKeySecret = expandEnvVar(config.AccessKeySecret)
|
||||
config.KnowledgeBaseID = expandEnvVar(config.KnowledgeBaseID)
|
||||
config.WorkspaceID = expandEnvVar(config.WorkspaceID)
|
||||
|
||||
// 设置默认值
|
||||
setDefaults(config)
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// simpleYAMLToJSON 简单的 YAML 到 JSON 转换
|
||||
// 注意:这是一个临时实现,仅支持基本的 YAML 格式
|
||||
// 生产环境请使用 gopkg.in/yaml.v3
|
||||
func simpleYAMLToJSON(yamlContent string) string {
|
||||
trimmed := strings.TrimSpace(yamlContent)
|
||||
|
||||
// 如果内容看起来像 JSON,直接返回
|
||||
if strings.HasPrefix(trimmed, "{") {
|
||||
return yamlContent
|
||||
}
|
||||
|
||||
// 否则返回默认禁用配置
|
||||
return `{"rag": {"enabled": false}}`
|
||||
}
|
||||
|
||||
// expandEnvVar 展开环境变量 ${VAR_NAME}
|
||||
func expandEnvVar(value string) string {
|
||||
if strings.HasPrefix(value, "${") && strings.HasSuffix(value, "}") {
|
||||
varName := value[2 : len(value)-1]
|
||||
return os.Getenv(varName)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// setDefaults 设置默认值
|
||||
func setDefaults(config *RAGConfig) {
|
||||
if config.ContextMode == "" {
|
||||
config.ContextMode = "full"
|
||||
}
|
||||
if config.MaxContextLength == 0 {
|
||||
config.MaxContextLength = 4000
|
||||
}
|
||||
if config.DefaultTopK == 0 {
|
||||
config.DefaultTopK = 3
|
||||
}
|
||||
if config.SimilarityThreshold == 0 {
|
||||
config.SimilarityThreshold = 0.7
|
||||
}
|
||||
if config.CacheTTL == 0 {
|
||||
config.CacheTTL = 3600
|
||||
}
|
||||
if config.CacheMaxSize == 0 {
|
||||
config.CacheMaxSize = 1000
|
||||
}
|
||||
if config.Timeout == 0 {
|
||||
config.Timeout = 10
|
||||
}
|
||||
if config.MaxRetries == 0 {
|
||||
config.MaxRetries = 3
|
||||
}
|
||||
if config.RetryDelay == 0 {
|
||||
config.RetryDelay = 1
|
||||
}
|
||||
|
||||
// 为每个工具配置设置默认值
|
||||
for _, toolConfig := range config.Tools {
|
||||
if toolConfig.ContextMode == "" {
|
||||
toolConfig.ContextMode = config.ContextMode
|
||||
}
|
||||
if toolConfig.TopK == 0 {
|
||||
toolConfig.TopK = config.DefaultTopK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetToolConfig 获取指定工具的配置
|
||||
func (c *RAGConfig) GetToolConfig(toolName string) *ToolConfig {
|
||||
if toolConfig, ok := c.Tools[toolName]; ok {
|
||||
return toolConfig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsToolRAGEnabled 检查指定工具是否启用 RAG
|
||||
func (c *RAGConfig) IsToolRAGEnabled(toolName string) bool {
|
||||
if !c.Enabled {
|
||||
return false
|
||||
}
|
||||
|
||||
toolConfig := c.GetToolConfig(toolName)
|
||||
if toolConfig == nil {
|
||||
// 没有工具级配置,使用全局配置
|
||||
return c.Enabled
|
||||
}
|
||||
|
||||
return toolConfig.UseRAG
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
// Package rag 提供 RAG(检索增强生成)功能
|
||||
// 支持可选的知识库集成,通过配置开关控制
|
||||
package rag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RAGManager 管理 RAG 功能的开关和查询
|
||||
type RAGManager struct {
|
||||
enabled bool // RAG 功能是否启用
|
||||
client *RAGClient // RAG 客户端(仅在 enabled=true 时有效)
|
||||
config *RAGConfig // 配置
|
||||
}
|
||||
|
||||
// NewRAGManager 创建 RAG 管理器
|
||||
// 如果配置中 enabled=false,则返回禁用状态的管理器
|
||||
func NewRAGManager(config *RAGConfig) *RAGManager {
|
||||
if config == nil || !config.Enabled {
|
||||
log.Println("📖 RAG: Disabled (using rule-based generation)")
|
||||
return &RAGManager{
|
||||
enabled: false,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// 验证必要配置
|
||||
if config.KnowledgeBaseID == "" || config.WorkspaceID == "" {
|
||||
log.Println("⚠️ RAG: Missing workspace ID or knowledge base ID, disabling RAG")
|
||||
return &RAGManager{
|
||||
enabled: false,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 SDK 认证凭证
|
||||
if config.AccessKeyID == "" || config.AccessKeySecret == "" {
|
||||
log.Println("⚠️ RAG: Missing AccessKey credentials, disabling RAG")
|
||||
return &RAGManager{
|
||||
enabled: false,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 RAG 客户端(使用 SDK)
|
||||
log.Println("🔧 RAG: Using Alibaba Cloud SDK authentication")
|
||||
client, err := NewRAGClient(config)
|
||||
if err != nil {
|
||||
log.Printf("❌ RAG: Failed to initialize SDK client: %v, disabling RAG\n", err)
|
||||
return &RAGManager{
|
||||
enabled: false,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("✅ RAG: Enabled (Provider: %s, KB: %s)\n", config.Provider, config.KnowledgeBaseID)
|
||||
|
||||
return &RAGManager{
|
||||
enabled: true,
|
||||
client: client,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled 返回 RAG 是否启用
|
||||
func (m *RAGManager) IsEnabled() bool {
|
||||
return m.enabled
|
||||
}
|
||||
|
||||
// QueryWithContext 查询知识库并返回上下文
|
||||
// 如果 RAG 未启用,返回空上下文(不报错)
|
||||
func (m *RAGManager) QueryWithContext(query string, scenario string, opts ...QueryOption) (*RAGContext, error) {
|
||||
// RAG 未启用,返回空上下文
|
||||
if !m.enabled {
|
||||
return &RAGContext{
|
||||
Enabled: false,
|
||||
Message: "RAG is disabled, using rule-based generation",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 构建查询
|
||||
ragQuery := &RAGQuery{
|
||||
Query: query,
|
||||
Scenario: scenario,
|
||||
TopK: m.config.DefaultTopK,
|
||||
}
|
||||
|
||||
// 应用可选参数
|
||||
for _, opt := range opts {
|
||||
opt(ragQuery)
|
||||
}
|
||||
|
||||
// 查询知识库
|
||||
resp, err := m.client.SearchWithCache(ragQuery)
|
||||
if err != nil {
|
||||
// 如果配置了降级策略,返回空上下文而不是报错
|
||||
if m.config.FallbackOnError {
|
||||
log.Printf("⚠️ RAG query failed, falling back to rules: %v\n", err)
|
||||
return &RAGContext{
|
||||
Enabled: false,
|
||||
Message: fmt.Sprintf("RAG query failed, using fallback: %v", err),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("RAG query failed: %w", err)
|
||||
}
|
||||
|
||||
// 构建上下文
|
||||
return m.buildContext(resp), nil
|
||||
}
|
||||
|
||||
// QueryForTool 为特定工具查询(支持工具级配置覆盖)
|
||||
// 这是工具级别配置的核心实现
|
||||
func (m *RAGManager) QueryForTool(toolName string, query string, scenario string) (*RAGContext, error) {
|
||||
// 全局 RAG 未启用
|
||||
if !m.enabled {
|
||||
return &RAGContext{
|
||||
Enabled: false,
|
||||
Message: "RAG is disabled globally",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 检查工具级配置
|
||||
if toolConfig, ok := m.config.Tools[toolName]; ok {
|
||||
// 工具有专门的配置
|
||||
if !toolConfig.UseRAG {
|
||||
// 工具明确不使用 RAG
|
||||
return &RAGContext{
|
||||
Enabled: false,
|
||||
Message: fmt.Sprintf("RAG is disabled for tool: %s", toolName),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 使用工具级配置覆盖全局配置
|
||||
log.Printf("🔧 Using tool-specific RAG config for: %s (context_mode=%s, top_k=%d)",
|
||||
toolName, toolConfig.ContextMode, toolConfig.TopK)
|
||||
|
||||
return m.QueryWithContext(query, scenario,
|
||||
WithTopK(toolConfig.TopK),
|
||||
WithContextMode(toolConfig.ContextMode),
|
||||
)
|
||||
}
|
||||
|
||||
// 没有工具级配置,使用默认全局配置
|
||||
log.Printf("🔧 Using global RAG config for: %s", toolName)
|
||||
return m.QueryWithContext(query, scenario)
|
||||
}
|
||||
|
||||
// buildContext 构建上下文
|
||||
func (m *RAGManager) buildContext(resp *RAGResponse) *RAGContext {
|
||||
ctx := &RAGContext{
|
||||
Enabled: true,
|
||||
Documents: make([]ContextDocument, 0, len(resp.Documents)),
|
||||
}
|
||||
|
||||
for _, doc := range resp.Documents {
|
||||
ctxDoc := ContextDocument{
|
||||
Title: doc.Title,
|
||||
Source: doc.Source,
|
||||
URL: doc.URL,
|
||||
Score: doc.Score,
|
||||
Highlights: doc.Highlights,
|
||||
}
|
||||
|
||||
// 根据 context_mode 决定返回的内容
|
||||
switch m.config.ContextMode {
|
||||
case "full":
|
||||
ctxDoc.Content = doc.Content
|
||||
case "summary":
|
||||
ctxDoc.Content = m.summarize(doc.Content)
|
||||
case "highlights":
|
||||
if len(doc.Highlights) > 0 {
|
||||
ctxDoc.Content = strings.Join(doc.Highlights, "\n\n")
|
||||
} else {
|
||||
ctxDoc.Content = m.summarize(doc.Content)
|
||||
}
|
||||
default:
|
||||
ctxDoc.Content = doc.Content
|
||||
}
|
||||
|
||||
// 控制长度
|
||||
if len(ctxDoc.Content) > m.config.MaxContextLength {
|
||||
ctxDoc.Content = ctxDoc.Content[:m.config.MaxContextLength] + "\n\n[内容已截断...]"
|
||||
}
|
||||
|
||||
ctx.Documents = append(ctx.Documents, ctxDoc)
|
||||
}
|
||||
|
||||
ctx.Message = fmt.Sprintf("Retrieved %d relevant documents from knowledge base (latency: %dms)",
|
||||
len(ctx.Documents), resp.Latency)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// summarize 简单的内容摘要(截取前N个字符)
|
||||
func (m *RAGManager) summarize(content string, maxLen ...int) string {
|
||||
length := 500 // 默认500字符
|
||||
if len(maxLen) > 0 {
|
||||
length = maxLen[0]
|
||||
}
|
||||
|
||||
if len(content) <= length {
|
||||
return content
|
||||
}
|
||||
|
||||
// 尝试在句号或换行处截断
|
||||
truncated := content[:length]
|
||||
if idx := strings.LastIndexAny(truncated, "。\n."); idx > length/2 {
|
||||
return content[:idx+1]
|
||||
}
|
||||
|
||||
return truncated + "..."
|
||||
}
|
||||
|
||||
// FormatContextForAI 格式化上下文,供 AI 使用
|
||||
// 返回 Markdown 格式的文档上下文
|
||||
func (ctx *RAGContext) FormatContextForAI() string {
|
||||
if !ctx.Enabled || len(ctx.Documents) == 0 {
|
||||
return fmt.Sprintf("> ℹ️ %s\n", ctx.Message)
|
||||
}
|
||||
|
||||
var result strings.Builder
|
||||
|
||||
result.WriteString("## 📚 知识库参考文档\n\n")
|
||||
result.WriteString(fmt.Sprintf("> %s\n\n", ctx.Message))
|
||||
|
||||
for i, doc := range ctx.Documents {
|
||||
result.WriteString(fmt.Sprintf("### 参考文档 %d: %s\n\n", i+1, doc.Title))
|
||||
|
||||
// 元信息
|
||||
result.WriteString(fmt.Sprintf("**来源**: %s \n", doc.Source))
|
||||
if doc.URL != "" {
|
||||
result.WriteString(fmt.Sprintf("**链接**: %s \n", doc.URL))
|
||||
}
|
||||
result.WriteString(fmt.Sprintf("**相关度**: %.2f \n\n", doc.Score))
|
||||
|
||||
// 文档内容(重点)
|
||||
result.WriteString("**相关内容**:\n\n")
|
||||
result.WriteString("```\n")
|
||||
result.WriteString(doc.Content)
|
||||
result.WriteString("\n```\n\n")
|
||||
|
||||
// 高亮片段
|
||||
if len(doc.Highlights) > 0 {
|
||||
result.WriteString("**关键片段**:\n\n")
|
||||
for _, h := range doc.Highlights {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", h))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
result.WriteString("---\n\n")
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
// RAGContext 表示 RAG 查询返回的上下文
|
||||
type RAGContext struct {
|
||||
Enabled bool `json:"enabled"` // RAG 是否启用
|
||||
Documents []ContextDocument `json:"documents"` // 检索到的文档
|
||||
Message string `json:"message"` // 提示信息
|
||||
}
|
||||
|
||||
// ContextDocument 表示上下文中的一个文档
|
||||
type ContextDocument struct {
|
||||
Title string `json:"title"` // 文档标题
|
||||
Content string `json:"content"` // 文档内容(根据 context_mode 调整)
|
||||
Source string `json:"source"` // 来源路径
|
||||
URL string `json:"url"` // 在线链接
|
||||
Score float64 `json:"score"` // 相关度分数
|
||||
Highlights []string `json:"highlights"` // 高亮片段
|
||||
}
|
||||
|
||||
// ==================== 查询选项 ====================
|
||||
|
||||
// QueryOption 查询选项函数
|
||||
type QueryOption func(*RAGQuery)
|
||||
|
||||
// WithTopK 设置返回文档数量
|
||||
func WithTopK(k int) QueryOption {
|
||||
return func(q *RAGQuery) {
|
||||
q.TopK = k
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextMode 设置上下文模式
|
||||
func WithContextMode(mode string) QueryOption {
|
||||
return func(q *RAGQuery) {
|
||||
q.ContextMode = mode
|
||||
}
|
||||
}
|
||||
|
||||
// WithFilters 设置过滤条件
|
||||
func WithFilters(filters map[string]interface{}) QueryOption {
|
||||
return func(q *RAGQuery) {
|
||||
q.Filters = filters
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,794 @@
|
||||
// MCP Server implementation for Nginx Migration Tools - Standalone Mode
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"nginx-migration-mcp/tools"
|
||||
)
|
||||
|
||||
// NewMCPServer creates a new MCP server instance
|
||||
func NewMCPServer(config *ServerConfig) *MCPServer {
|
||||
return &MCPServer{config: config}
|
||||
}
|
||||
|
||||
// HandleMessage processes an incoming MCP message
|
||||
func (s *MCPServer) HandleMessage(msg MCPMessage) MCPMessage {
|
||||
switch msg.Method {
|
||||
case "initialize":
|
||||
return s.handleInitialize(msg)
|
||||
case "tools/list":
|
||||
return s.handleToolsList(msg)
|
||||
case "tools/call":
|
||||
return s.handleToolsCall(msg)
|
||||
default:
|
||||
return s.errorResponse(msg.ID, -32601, "Method not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) handleInitialize(msg MCPMessage) MCPMessage {
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: msg.ID,
|
||||
Result: map[string]interface{}{
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": map[string]interface{}{
|
||||
"tools": map[string]interface{}{
|
||||
"listChanged": true,
|
||||
},
|
||||
},
|
||||
"serverInfo": map[string]interface{}{
|
||||
"name": s.config.Server.Name,
|
||||
"version": s.config.Server.Version,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) handleToolsList(msg MCPMessage) MCPMessage {
|
||||
toolsList := tools.GetMCPTools()
|
||||
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: msg.ID,
|
||||
Result: map[string]interface{}{
|
||||
"tools": toolsList,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) handleToolsCall(msg MCPMessage) MCPMessage {
|
||||
var params CallToolParams
|
||||
paramsBytes, _ := json.Marshal(msg.Params)
|
||||
json.Unmarshal(paramsBytes, ¶ms)
|
||||
|
||||
handlers := tools.GetToolHandlers(s)
|
||||
handler, exists := handlers[params.Name]
|
||||
|
||||
if !exists {
|
||||
return s.errorResponse(msg.ID, -32601, fmt.Sprintf("Unknown tool: %s", params.Name))
|
||||
}
|
||||
|
||||
result := handler(params.Arguments)
|
||||
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: msg.ID,
|
||||
Result: result,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) errorResponse(id interface{}, code int, message string) MCPMessage {
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: id,
|
||||
Error: &MCPError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Tool implementations
|
||||
|
||||
func (s *MCPServer) parseNginxConfig(args map[string]interface{}) tools.ToolResult {
|
||||
configContent, ok := args["config_content"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}}
|
||||
}
|
||||
|
||||
serverCount := strings.Count(configContent, "server {")
|
||||
locationCount := strings.Count(configContent, "location")
|
||||
hasSSL := strings.Contains(configContent, "ssl")
|
||||
hasProxy := strings.Contains(configContent, "proxy_pass")
|
||||
hasRewrite := strings.Contains(configContent, "rewrite")
|
||||
|
||||
complexity := "Simple"
|
||||
if serverCount > 1 || (hasRewrite && hasSSL) {
|
||||
complexity = "Complex"
|
||||
} else if hasRewrite || hasSSL {
|
||||
complexity = "Medium"
|
||||
}
|
||||
|
||||
analysis := fmt.Sprintf(`Nginx配置分析结果
|
||||
|
||||
基础信息:
|
||||
- Server块: %d个
|
||||
- Location块: %d个
|
||||
- SSL配置: %t
|
||||
- 反向代理: %t
|
||||
- URL重写: %t
|
||||
|
||||
复杂度: %s
|
||||
|
||||
迁移建议:`, serverCount, locationCount, hasSSL, hasProxy, hasRewrite, complexity)
|
||||
|
||||
if hasProxy {
|
||||
analysis += "\n- 反向代理将转换为HTTPRoute backendRefs"
|
||||
}
|
||||
if hasRewrite {
|
||||
analysis += "\n- URL重写将使用URLRewrite过滤器"
|
||||
}
|
||||
if hasSSL {
|
||||
analysis += "\n- SSL配置需要迁移到Gateway资源"
|
||||
}
|
||||
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: analysis}}}
|
||||
}
|
||||
|
||||
func (s *MCPServer) convertToHigress(args map[string]interface{}) tools.ToolResult {
|
||||
configContent, ok := args["config_content"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}}
|
||||
}
|
||||
|
||||
namespace := s.config.Defaults.Namespace
|
||||
if ns, ok := args["namespace"].(string); ok {
|
||||
namespace = ns
|
||||
}
|
||||
|
||||
hostname := s.config.Defaults.Hostname
|
||||
lines := strings.Split(configContent, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "server_name") && !strings.Contains(line, "#") {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 {
|
||||
hostname = strings.TrimSuffix(parts[1], ";")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yamlConfig := fmt.Sprintf(`转换后的Higress配置
|
||||
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: %s
|
||||
namespace: %s
|
||||
annotations:
|
||||
higress.io/migrated-from: "nginx"
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: %s
|
||||
namespace: %s
|
||||
hostnames:
|
||||
- %s
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: %s
|
||||
backendRefs:
|
||||
- name: %s
|
||||
port: %d
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: %s
|
||||
namespace: %s
|
||||
spec:
|
||||
selector:
|
||||
app: backend
|
||||
ports:
|
||||
- port: %d
|
||||
targetPort: %d
|
||||
|
||||
转换完成
|
||||
|
||||
应用步骤:
|
||||
1. 保存为 higress-config.yaml
|
||||
2. 执行: kubectl apply -f higress-config.yaml
|
||||
3. 验证: kubectl get httproute -n %s`,
|
||||
s.config.GenerateRouteName(hostname), namespace,
|
||||
s.config.Gateway.Name, s.config.Gateway.Namespace, hostname, s.config.Defaults.PathPrefix,
|
||||
s.config.GenerateServiceName(hostname), s.config.Service.DefaultPort,
|
||||
s.config.GenerateServiceName(hostname), namespace,
|
||||
s.config.Service.DefaultPort, s.config.Service.DefaultTarget, namespace)
|
||||
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: yamlConfig}}}
|
||||
}
|
||||
|
||||
func (s *MCPServer) analyzeLuaPlugin(args map[string]interface{}) tools.ToolResult {
|
||||
luaCode, ok := args["lua_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}}
|
||||
}
|
||||
|
||||
// 使用新的 AI 友好分析
|
||||
analysis := tools.AnalyzeLuaPluginForAI(luaCode)
|
||||
|
||||
// 生成用户友好的消息
|
||||
features := []string{}
|
||||
for feature := range analysis.Features {
|
||||
features = append(features, fmt.Sprintf("- %s", feature))
|
||||
}
|
||||
|
||||
userMessage := fmt.Sprintf(`✅ Lua 插件分析完成
|
||||
|
||||
📊 **检测到的特性**:
|
||||
%s
|
||||
|
||||
⚠️ **兼容性警告**:
|
||||
%s
|
||||
|
||||
📈 **复杂度**:%s
|
||||
🔄 **兼容性级别**:%s
|
||||
|
||||
💡 **迁移建议**:`,
|
||||
strings.Join(features, "\n"),
|
||||
strings.Join(analysis.Warnings, "\n- "),
|
||||
analysis.Complexity,
|
||||
analysis.Compatibility,
|
||||
)
|
||||
|
||||
switch analysis.Compatibility {
|
||||
case "full":
|
||||
userMessage += "\n- 可直接迁移到 WASM 插件\n- 建议使用工具链进行转换"
|
||||
case "partial":
|
||||
userMessage += "\n- 需要部分重构\n- 强烈建议使用工具链并让 AI 参与代码生成"
|
||||
case "manual":
|
||||
userMessage += "\n- 需要手动重写\n- 建议分步骤进行,使用工具链辅助"
|
||||
}
|
||||
|
||||
userMessage += "\n\n🔗 **后续操作**:\n"
|
||||
userMessage += "1. 调用 `generate_conversion_hints` 工具获取详细的转换提示\n"
|
||||
userMessage += "2. 基于提示生成 Go WASM 代码\n"
|
||||
userMessage += "3. 调用 `validate_wasm_code` 工具验证生成的代码\n"
|
||||
userMessage += "4. 调用 `generate_deployment_config` 工具生成部署配置\n"
|
||||
userMessage += "\n或者直接使用 `convert_lua_to_wasm` 进行一键转换。"
|
||||
|
||||
// 生成 AI 指令
|
||||
aiInstructions := fmt.Sprintf(`你现在已经获得了 Lua 插件的分析结果。基于这些信息,你可以:
|
||||
|
||||
### 选项 1:使用工具链进行精细控制
|
||||
|
||||
调用 generate_conversion_hints 工具,传入以下分析结果:
|
||||
`+"```json"+`
|
||||
{
|
||||
"analysis_result": %s,
|
||||
"plugin_name": "your-plugin-name"
|
||||
}
|
||||
`+"```"+`
|
||||
|
||||
这将为你提供代码生成模板,然后基于模板生成 Go WASM 代码。
|
||||
|
||||
### 选项 2:一键转换
|
||||
|
||||
如果用户希望快速转换,可以直接调用 convert_lua_to_wasm 工具。
|
||||
|
||||
### 建议的对话流程
|
||||
|
||||
1. **询问用户**:是否需要详细的转换提示,还是直接生成代码?
|
||||
2. **如果需要提示**:调用 generate_conversion_hints
|
||||
3. **生成代码后**:询问是否需要验证(调用 validate_wasm_code)
|
||||
4. **验证通过后**:询问是否需要生成部署配置(调用 generate_deployment_config)
|
||||
|
||||
### 关键注意事项
|
||||
|
||||
%s
|
||||
|
||||
### 代码生成要点
|
||||
|
||||
- 检测到的 Nginx 变量需要映射到 HTTP 头部
|
||||
- 复杂度为 %s,请相应调整代码结构
|
||||
- 兼容性级别为 %s,注意处理警告中的问题
|
||||
`,
|
||||
string(mustMarshalJSON(analysis)),
|
||||
formatWarningsForAI(analysis.Warnings),
|
||||
analysis.Complexity,
|
||||
analysis.Compatibility,
|
||||
)
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, analysis)
|
||||
}
|
||||
|
||||
func mustMarshalJSON(v interface{}) []byte {
|
||||
data, _ := json.Marshal(v)
|
||||
return data
|
||||
}
|
||||
|
||||
func formatWarningsForAI(warnings []string) string {
|
||||
if len(warnings) == 0 {
|
||||
return "- 无特殊警告,可以直接转换"
|
||||
}
|
||||
result := []string{}
|
||||
for _, w := range warnings {
|
||||
result = append(result, fmt.Sprintf("- ⚠️ %s", w))
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
func (s *MCPServer) convertLuaToWasm(args map[string]interface{}) tools.ToolResult {
|
||||
luaCode, ok := args["lua_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}}
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
analyzer := tools.AnalyzeLuaScript(luaCode)
|
||||
result, err := tools.ConvertLuaToWasm(analyzer, pluginName)
|
||||
if err != nil {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error: %v", err)}}}
|
||||
}
|
||||
|
||||
response := fmt.Sprintf(`Lua脚本转换完成
|
||||
|
||||
转换分析:
|
||||
- 复杂度: %s
|
||||
- 检测特性: %d个
|
||||
- 兼容性警告: %d个
|
||||
|
||||
注意事项:
|
||||
%s
|
||||
|
||||
生成的文件:
|
||||
|
||||
==== main.go ====
|
||||
%s
|
||||
|
||||
==== WasmPlugin配置 ====
|
||||
%s
|
||||
|
||||
部署步骤:
|
||||
1. 创建插件目录: mkdir -p extensions/%s
|
||||
2. 保存Go代码到: extensions/%s/main.go
|
||||
3. 构建插件: PLUGIN_NAME=%s make build
|
||||
4. 应用配置: kubectl apply -f wasmplugin.yaml
|
||||
|
||||
提示:
|
||||
- 请根据实际需求调整配置
|
||||
- 测试插件功能后再部署到生产环境
|
||||
- 如有共享状态需求,请配置Redis等外部存储
|
||||
`,
|
||||
analyzer.Complexity,
|
||||
len(analyzer.Features),
|
||||
len(analyzer.Warnings),
|
||||
strings.Join(analyzer.Warnings, "\n- "),
|
||||
result.GoCode,
|
||||
result.WasmPluginYAML,
|
||||
pluginName, pluginName, pluginName)
|
||||
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: response}}}
|
||||
}
|
||||
|
||||
// GenerateConversionHints 生成详细的代码转换提示
|
||||
func (s *MCPServer) GenerateConversionHints(args map[string]interface{}) tools.ToolResult {
|
||||
analysisResultStr, ok := args["analysis_result"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing analysis_result"}}}
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
// 解析分析结果
|
||||
var analysis tools.AnalysisResultForAI
|
||||
if err := json.Unmarshal([]byte(analysisResultStr), &analysis); err != nil {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error parsing analysis_result: %v", err)}}}
|
||||
}
|
||||
|
||||
// 生成转换提示
|
||||
hints := tools.GenerateConversionHints(analysis, pluginName)
|
||||
|
||||
// 格式化输出
|
||||
userMessage := fmt.Sprintf(`🎯 代码转换提示
|
||||
|
||||
**插件名称**: %s
|
||||
**代码模板**: %s
|
||||
|
||||
%s
|
||||
`,
|
||||
pluginName,
|
||||
hints.CodeTemplate,
|
||||
func() string {
|
||||
if len(hints.Warnings) > 0 {
|
||||
return "⚠️ **警告**: " + formatWarningsListForUser(hints.Warnings)
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
)
|
||||
|
||||
// 生成详细的 AI 指令
|
||||
aiInstructions := fmt.Sprintf(`现在你需要基于以下信息生成 Go WASM 插件代码。
|
||||
|
||||
## 代码模板
|
||||
|
||||
%s
|
||||
|
||||
## 生成代码的要求
|
||||
|
||||
1. **实现所需的回调函数**
|
||||
2. **保持 Lua 代码的业务逻辑等价**
|
||||
3. **添加适当的错误处理**
|
||||
4. **包含配置解析逻辑(如需要)**
|
||||
|
||||
## 输出格式
|
||||
|
||||
请按以下格式输出代码:
|
||||
|
||||
### main.go
|
||||
`+"```go"+`
|
||||
[完整的 Go 代码]
|
||||
`+"```"+`
|
||||
|
||||
生成代码后,建议调用 validate_wasm_code 工具进行验证。
|
||||
`,
|
||||
hints.CodeTemplate,
|
||||
)
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, hints)
|
||||
}
|
||||
|
||||
// ValidateWasmCode 验证生成的 Go WASM 代码
|
||||
func (s *MCPServer) ValidateWasmCode(args map[string]interface{}) tools.ToolResult {
|
||||
goCode, ok := args["go_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}}
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
// 执行验证
|
||||
report := tools.ValidateWasmCode(goCode, pluginName)
|
||||
|
||||
// 统计各类问题数量
|
||||
requiredCount := 0
|
||||
recommendedCount := 0
|
||||
optionalCount := 0
|
||||
bestPracticeCount := 0
|
||||
|
||||
for _, issue := range report.Issues {
|
||||
switch issue.Category {
|
||||
case "required":
|
||||
requiredCount++
|
||||
case "recommended":
|
||||
recommendedCount++
|
||||
case "optional":
|
||||
optionalCount++
|
||||
case "best_practice":
|
||||
bestPracticeCount++
|
||||
}
|
||||
}
|
||||
|
||||
// 构建用户消息
|
||||
userMessage := fmt.Sprintf(`## 代码验证报告
|
||||
|
||||
%s
|
||||
|
||||
### 发现的回调函数 (%d 个)
|
||||
%s
|
||||
|
||||
### 配置结构
|
||||
%s
|
||||
|
||||
### 问题分类
|
||||
|
||||
#### 必须修复 (%d 个)
|
||||
%s
|
||||
|
||||
#### 建议修复 (%d 个)
|
||||
%s
|
||||
|
||||
#### 可选优化 (%d 个)
|
||||
%s
|
||||
|
||||
#### 最佳实践 (%d 个)
|
||||
%s
|
||||
|
||||
### 缺失的导入包 (%d 个)
|
||||
%s
|
||||
|
||||
---
|
||||
|
||||
`,
|
||||
report.Summary,
|
||||
len(report.FoundCallbacks),
|
||||
formatCallbacksList(report.FoundCallbacks),
|
||||
formatConfigStatus(report.HasConfig),
|
||||
requiredCount,
|
||||
formatIssuesByCategory(report.Issues, "required"),
|
||||
recommendedCount,
|
||||
formatIssuesByCategory(report.Issues, "recommended"),
|
||||
optionalCount,
|
||||
formatIssuesByCategory(report.Issues, "optional"),
|
||||
bestPracticeCount,
|
||||
formatIssuesByCategory(report.Issues, "best_practice"),
|
||||
len(report.MissingImports),
|
||||
formatList(report.MissingImports),
|
||||
)
|
||||
|
||||
// 根据问题级别给出建议
|
||||
hasRequired := requiredCount > 0
|
||||
if hasRequired {
|
||||
userMessage += " **请优先修复 \"必须修复\" 的问题,否则代码可能无法编译或运行。**\n\n"
|
||||
} else if recommendedCount > 0 {
|
||||
userMessage += " **代码基本结构正确。** 建议修复 \"建议修复\" 的问题以提高代码质量。\n\n"
|
||||
} else {
|
||||
userMessage += " **代码验证通过!** 可以继续生成部署配置。\n\n"
|
||||
userMessage += "**下一步**:调用 `generate_deployment_config` 工具生成部署配置。\n"
|
||||
}
|
||||
|
||||
// AI 指令
|
||||
aiInstructions := ""
|
||||
if hasRequired {
|
||||
aiInstructions = `代码验证发现必须修复的问题。
|
||||
|
||||
## 修复指南
|
||||
|
||||
` + formatIssuesForAI(report.Issues, "required") + `
|
||||
|
||||
请修复上述问题后,再次调用 validate_wasm_code 工具进行验证。
|
||||
`
|
||||
} else if recommendedCount > 0 {
|
||||
aiInstructions = `代码基本结构正确,建议修复以下问题:
|
||||
|
||||
` + formatIssuesForAI(report.Issues, "recommended") + `
|
||||
|
||||
可以选择修复这些问题,或直接调用 generate_deployment_config 工具生成部署配置。
|
||||
`
|
||||
} else {
|
||||
aiInstructions = `代码验证通过!
|
||||
|
||||
## 下一步
|
||||
|
||||
调用 generate_deployment_config 工具,参数:
|
||||
` + "```json" + `
|
||||
{
|
||||
"plugin_name": "` + pluginName + `",
|
||||
"go_code": "[验证通过的代码]",
|
||||
"namespace": "higress-system"
|
||||
}
|
||||
` + "```" + `
|
||||
|
||||
这将生成完整的部署配置包。
|
||||
`
|
||||
}
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, report)
|
||||
}
|
||||
|
||||
// GenerateDeploymentConfig 生成部署配置
|
||||
func (s *MCPServer) GenerateDeploymentConfig(args map[string]interface{}) tools.ToolResult {
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
goCode, ok := args["go_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}}
|
||||
}
|
||||
|
||||
namespace := "higress-system"
|
||||
if ns, ok := args["namespace"].(string); ok && ns != "" {
|
||||
namespace = ns
|
||||
}
|
||||
|
||||
configSchema := ""
|
||||
if cs, ok := args["config_schema"].(string); ok {
|
||||
configSchema = cs
|
||||
}
|
||||
|
||||
// 生成部署包
|
||||
pkg := tools.GenerateDeploymentPackage(pluginName, goCode, configSchema, namespace)
|
||||
|
||||
// 格式化输出
|
||||
userMessage := fmt.Sprintf(`🎉 部署配置生成完成!
|
||||
|
||||
已为插件 **%s** 生成完整的部署配置包。
|
||||
|
||||
## 生成的文件
|
||||
|
||||
### 1. WasmPlugin 配置
|
||||
- 文件名:wasmplugin.yaml
|
||||
- 命名空间:%s
|
||||
- 包含默认配置和匹配规则
|
||||
|
||||
### 2. 构建脚本
|
||||
- Makefile:自动化构建和部署
|
||||
- Dockerfile:容器化打包
|
||||
|
||||
### 3. 文档
|
||||
- README.md:完整的使用说明
|
||||
- 包含快速开始、配置说明、问题排查
|
||||
|
||||
### 4. 测试脚本
|
||||
- test.sh:自动化测试脚本
|
||||
|
||||
### 5. 依赖清单
|
||||
- 列出了所有必需的 Go 模块
|
||||
|
||||
---
|
||||
|
||||
## 快速部署
|
||||
|
||||
`+"```bash"+`
|
||||
# 1. 保存文件
|
||||
# 保存 main.go
|
||||
# 保存 wasmplugin.yaml
|
||||
# 保存 Makefile
|
||||
# 保存 Dockerfile
|
||||
|
||||
# 2. 构建插件
|
||||
make build
|
||||
|
||||
# 3. 构建并推送镜像
|
||||
make docker-build docker-push
|
||||
|
||||
# 4. 部署到 Kubernetes
|
||||
make deploy
|
||||
|
||||
# 5. 验证部署
|
||||
kubectl get wasmplugin -n %s
|
||||
`+"```"+`
|
||||
|
||||
---
|
||||
|
||||
**文件内容请见下方结构化数据部分。**
|
||||
`,
|
||||
pluginName,
|
||||
namespace,
|
||||
namespace,
|
||||
)
|
||||
|
||||
aiInstructions := fmt.Sprintf(`部署配置已生成完毕。
|
||||
|
||||
## 向用户展示文件
|
||||
|
||||
请将以下文件内容清晰地展示给用户:
|
||||
|
||||
### 1. main.go
|
||||
用户已经有这个文件。
|
||||
|
||||
### 2. wasmplugin.yaml
|
||||
`+"```yaml"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### 3. Makefile
|
||||
`+"```makefile"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### 4. Dockerfile
|
||||
`+"```dockerfile"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### 5. README.md
|
||||
`+"```markdown"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### 6. test.sh
|
||||
`+"```bash"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
## 后续支持
|
||||
|
||||
询问用户是否需要:
|
||||
1. 解释任何配置项的含义
|
||||
2. 自定义某些配置
|
||||
3. 帮助解决部署问题
|
||||
`,
|
||||
pkg.WasmPluginYAML,
|
||||
pkg.Makefile,
|
||||
pkg.Dockerfile,
|
||||
pkg.README,
|
||||
pkg.TestScript,
|
||||
)
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, aiInstructions, pkg)
|
||||
}
|
||||
|
||||
// 辅助格式化函数
|
||||
|
||||
func formatWarningsListForUser(warnings []string) string {
|
||||
if len(warnings) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return strings.Join(warnings, "\n- ")
|
||||
}
|
||||
|
||||
func formatCallbacksList(callbacks []string) string {
|
||||
if len(callbacks) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return "- " + strings.Join(callbacks, "\n- ")
|
||||
}
|
||||
|
||||
func formatConfigStatus(hasConfig bool) string {
|
||||
if hasConfig {
|
||||
return " 已定义配置结构体"
|
||||
}
|
||||
return "- 未定义配置结构体(如不需要配置可忽略)"
|
||||
}
|
||||
|
||||
func formatIssuesByCategory(issues []tools.ValidationIssue, category string) string {
|
||||
var filtered []string
|
||||
for _, issue := range issues {
|
||||
if issue.Category == category {
|
||||
filtered = append(filtered, fmt.Sprintf("- **[%s]** %s\n 💡 建议: %s\n 📌 影响: %s",
|
||||
issue.Type, issue.Message, issue.Suggestion, issue.Impact))
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return strings.Join(filtered, "\n\n")
|
||||
}
|
||||
|
||||
func formatIssuesForAI(issues []tools.ValidationIssue, category string) string {
|
||||
var filtered []tools.ValidationIssue
|
||||
for _, issue := range issues {
|
||||
if issue.Category == category {
|
||||
filtered = append(filtered, issue)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filtered) == 0 {
|
||||
return "无问题"
|
||||
}
|
||||
|
||||
result := []string{}
|
||||
for i, issue := range filtered {
|
||||
result = append(result, fmt.Sprintf(`
|
||||
### 问题 %d: %s
|
||||
|
||||
**类型**: %s
|
||||
**建议**: %s
|
||||
**影响**: %s
|
||||
|
||||
请根据建议修复此问题。
|
||||
`,
|
||||
i+1,
|
||||
issue.Message,
|
||||
issue.Type,
|
||||
issue.Suggestion,
|
||||
issue.Impact,
|
||||
))
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
func formatList(items []string) string {
|
||||
if len(items) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return "- " + strings.Join(items, "\n- ")
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"name": "nginx-migration",
|
||||
"description": "Nginx 到 Higress 迁移工具集:支持配置转换和 Lua 插件迁移",
|
||||
"tools": [
|
||||
{
|
||||
"name": "parse_nginx_config",
|
||||
"description": "解析和分析 Nginx 配置文件,识别配置结构和复杂度",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config_content": {
|
||||
"type": "string",
|
||||
"description": "Nginx 配置文件内容"
|
||||
}
|
||||
},
|
||||
"required": ["config_content"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "convert_to_higress",
|
||||
"description": "智能解析 Nginx 配置并通过 AI 推理生成 Higress Ingress/HTTPRoute 配置。支持复杂配置(SSL、重写、重定向、upstream 等)的智能转换,结合 RAG 知识库提供准确的转换方案",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config_content": {
|
||||
"type": "string",
|
||||
"description": "Nginx 配置文件内容"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "目标 Kubernetes 命名空间",
|
||||
"default": "default"
|
||||
},
|
||||
"use_gateway_api": {
|
||||
"type": "boolean",
|
||||
"description": "是否使用 Gateway API (HTTPRoute)。默认 false,使用 Ingress",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"required": ["config_content"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "analyze_lua_plugin",
|
||||
"description": "分析 Nginx Lua 插件的兼容性,识别使用的 API 和潜在迁移问题,返回结构化分析结果供后续工具使用",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lua_code": {
|
||||
"type": "string",
|
||||
"description": "Nginx Lua 插件代码"
|
||||
}
|
||||
},
|
||||
"required": ["lua_code"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "generate_conversion_hints",
|
||||
"description": "基于 Lua 分析结果生成代码转换模板",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"analysis_result": {
|
||||
"type": "string",
|
||||
"description": "analyze_lua_plugin 返回的 JSON 格式分析结果"
|
||||
},
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "目标插件名称(小写字母和连字符)"
|
||||
}
|
||||
},
|
||||
"required": ["analysis_result", "plugin_name"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "validate_wasm_code",
|
||||
"description": "验证生成的 Go WASM 插件代码,检查语法、API 使用、配置结构等,输出验证报告和改进建议",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"go_code": {
|
||||
"type": "string",
|
||||
"description": "生成的 Go WASM 插件代码"
|
||||
},
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "插件名称"
|
||||
}
|
||||
},
|
||||
"required": ["go_code", "plugin_name"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "generate_deployment_config",
|
||||
"description": "为验证通过的 WASM 插件生成完整的部署配置包,包括 WasmPlugin YAML、Makefile、Dockerfile、README 和测试脚本",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "插件名称"
|
||||
},
|
||||
"go_code": {
|
||||
"type": "string",
|
||||
"description": "验证通过的 Go 代码"
|
||||
},
|
||||
"config_schema": {
|
||||
"type": "string",
|
||||
"description": "配置 JSON Schema(可选)"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "部署命名空间",
|
||||
"default": "higress-system"
|
||||
}
|
||||
},
|
||||
"required": ["plugin_name", "go_code"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "convert_lua_to_wasm",
|
||||
"description": "一键将 Nginx Lua 脚本转换为 Higress WASM 插件,自动生成 Go 代码和 WasmPlugin 配置。适合简单插件快速转换",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lua_code": {
|
||||
"type": "string",
|
||||
"description": "要转换的 Nginx Lua 插件代码"
|
||||
},
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "生成的 WASM 插件名称 (小写字母和连字符)"
|
||||
}
|
||||
},
|
||||
"required": ["lua_code", "plugin_name"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"nginx-migration-mcp/standalone"
|
||||
)
|
||||
|
||||
const Version = "1.0.0"
|
||||
|
||||
func main() {
|
||||
// Load config
|
||||
config := standalone.LoadConfig()
|
||||
|
||||
server := standalone.NewMCPServer(config)
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
writer := bufio.NewWriter(os.Stdout)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Bytes()
|
||||
|
||||
var msg standalone.MCPMessage
|
||||
if err := json.Unmarshal(line, &msg); err != nil {
|
||||
log.Printf("Error parsing message: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
response := server.HandleMessage(msg)
|
||||
responseBytes, _ := json.Marshal(response)
|
||||
|
||||
writer.Write(responseBytes)
|
||||
writer.WriteByte('\n')
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading from stdin: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Configuration management for nginx migration MCP server - Standalone Mode
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ServerConfig holds all configurable values
|
||||
type ServerConfig struct {
|
||||
Server ServerSettings `json:"server"`
|
||||
Gateway GatewaySettings `json:"gateway"`
|
||||
Service ServiceSettings `json:"service"`
|
||||
Defaults DefaultSettings `json:"defaults"`
|
||||
}
|
||||
|
||||
type ServerSettings struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Port string `json:"port"`
|
||||
APIBaseURL string `json:"api_base_url"`
|
||||
}
|
||||
|
||||
type GatewaySettings struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
}
|
||||
|
||||
type ServiceSettings struct {
|
||||
DefaultName string `json:"default_name"`
|
||||
DefaultPort int `json:"default_port"`
|
||||
DefaultTarget int `json:"default_target_port"`
|
||||
}
|
||||
|
||||
type DefaultSettings struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Namespace string `json:"namespace"`
|
||||
PathPrefix string `json:"path_prefix"`
|
||||
RoutePrefix string `json:"route_prefix"`
|
||||
}
|
||||
|
||||
// LoadConfig loads configuration from environment variables and files
|
||||
func LoadConfig() *ServerConfig {
|
||||
config := &ServerConfig{
|
||||
Server: ServerSettings{
|
||||
Name: getEnvOrDefault("NGINX_MCP_SERVER_NAME", "nginx-migration-mcp"),
|
||||
Version: getEnvOrDefault("NGINX_MCP_VERSION", "1.0.0"),
|
||||
Port: getEnvOrDefault("NGINX_MCP_PORT", "8080"),
|
||||
APIBaseURL: getEnvOrDefault("NGINX_MIGRATION_API_URL", "http://localhost:8080"),
|
||||
},
|
||||
Gateway: GatewaySettings{
|
||||
Name: getEnvOrDefault("HIGRESS_GATEWAY_NAME", "higress-gateway"),
|
||||
Namespace: getEnvOrDefault("HIGRESS_GATEWAY_NAMESPACE", "higress-system"),
|
||||
},
|
||||
Service: ServiceSettings{
|
||||
DefaultName: getEnvOrDefault("DEFAULT_SERVICE_NAME", "backend-service"),
|
||||
DefaultPort: getIntEnvOrDefault("DEFAULT_SERVICE_PORT", 80),
|
||||
DefaultTarget: getIntEnvOrDefault("DEFAULT_TARGET_PORT", 8080),
|
||||
},
|
||||
Defaults: DefaultSettings{
|
||||
Hostname: getEnvOrDefault("DEFAULT_HOSTNAME", "example.com"),
|
||||
Namespace: getEnvOrDefault("DEFAULT_NAMESPACE", "default"),
|
||||
PathPrefix: getEnvOrDefault("DEFAULT_PATH_PREFIX", "/"),
|
||||
RoutePrefix: getEnvOrDefault("ROUTE_NAME_PREFIX", "nginx-migrated"),
|
||||
},
|
||||
}
|
||||
|
||||
// Try to load from config file if exists
|
||||
if configFile := os.Getenv("NGINX_MCP_CONFIG_FILE"); configFile != "" {
|
||||
if err := loadConfigFromFile(config, configFile); err != nil {
|
||||
fmt.Printf("Warning: Failed to load config from %s: %v\n", configFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// loadConfigFromFile loads configuration from JSON file
|
||||
func loadConfigFromFile(config *ServerConfig, filename string) error {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, config)
|
||||
}
|
||||
|
||||
// getEnvOrDefault returns environment variable value or default
|
||||
func getEnvOrDefault(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// getIntEnvOrDefault returns environment variable as int or default
|
||||
func getIntEnvOrDefault(key string, defaultValue int) int {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
if intValue, err := strconv.Atoi(value); err == nil {
|
||||
return intValue
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GenerateRouteName generates a unique route name
|
||||
func (c *ServerConfig) GenerateRouteName(hostname string) string {
|
||||
if hostname == "" || hostname == c.Defaults.Hostname {
|
||||
return fmt.Sprintf("%s-route", c.Defaults.RoutePrefix)
|
||||
}
|
||||
// Replace dots and special characters for valid k8s name
|
||||
safeName := hostname
|
||||
for _, char := range []string{".", "_", ":"} {
|
||||
safeName = strings.ReplaceAll(safeName, char, "-")
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", c.Defaults.RoutePrefix, safeName)
|
||||
}
|
||||
|
||||
// GenerateIngressName generates a unique ingress name
|
||||
func (c *ServerConfig) GenerateIngressName(hostname string) string {
|
||||
if hostname == "" || hostname == c.Defaults.Hostname {
|
||||
return fmt.Sprintf("%s-ingress", c.Defaults.RoutePrefix)
|
||||
}
|
||||
// Replace dots and special characters for valid k8s name
|
||||
safeName := hostname
|
||||
for _, char := range []string{".", "_", ":"} {
|
||||
safeName = strings.ReplaceAll(safeName, char, "-")
|
||||
}
|
||||
return fmt.Sprintf("%s-%s", c.Defaults.RoutePrefix, safeName)
|
||||
}
|
||||
|
||||
// GenerateServiceName generates service name based on hostname
|
||||
func (c *ServerConfig) GenerateServiceName(hostname string) string {
|
||||
if hostname == "" || hostname == c.Defaults.Hostname {
|
||||
return c.Service.DefaultName
|
||||
}
|
||||
return fmt.Sprintf("%s-service", hostname)
|
||||
}
|
||||
@@ -0,0 +1,914 @@
|
||||
// Package standalone implements MCP Server for Nginx Migration Tools in standalone mode.
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"nginx-migration-mcp/internal/rag"
|
||||
"nginx-migration-mcp/tools"
|
||||
)
|
||||
|
||||
// NewMCPServer creates a new MCP server instance
|
||||
func NewMCPServer(config *ServerConfig) *MCPServer {
|
||||
// 初始化 RAG 管理器
|
||||
// 获取可执行文件所在目录
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Printf("WARNING: Failed to get executable path: %v", err)
|
||||
execPath = "."
|
||||
}
|
||||
execDir := filepath.Dir(execPath)
|
||||
|
||||
// 尝试多个可能的配置文件路径(相对于可执行文件)
|
||||
ragConfigPaths := []string{
|
||||
filepath.Join(execDir, "config", "rag.json"), // 同级 config 目录
|
||||
filepath.Join(execDir, "..", "config", "rag.json"), // 上级 config 目录
|
||||
"config/rag.json", // 当前工作目录
|
||||
}
|
||||
|
||||
var ragConfig *rag.RAGConfig
|
||||
var configErr error
|
||||
|
||||
for _, path := range ragConfigPaths {
|
||||
ragConfig, configErr = rag.LoadRAGConfig(path)
|
||||
if configErr == nil {
|
||||
log.Printf("Loaded RAG config from: %s", path)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if configErr != nil {
|
||||
log.Printf("WARNING: Failed to load RAG config: %v, RAG will be disabled", configErr)
|
||||
ragConfig = &rag.RAGConfig{Enabled: false}
|
||||
}
|
||||
|
||||
ragManager := rag.NewRAGManager(ragConfig)
|
||||
|
||||
if ragManager.IsEnabled() {
|
||||
log.Printf("RAG Manager initialized and enabled")
|
||||
} else {
|
||||
log.Printf("RAG Manager disabled, using rule-based approach")
|
||||
}
|
||||
|
||||
return &MCPServer{
|
||||
config: config,
|
||||
ragManager: ragManager,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleMessage processes an incoming MCP message
|
||||
func (s *MCPServer) HandleMessage(msg MCPMessage) MCPMessage {
|
||||
switch msg.Method {
|
||||
case "initialize":
|
||||
return s.handleInitialize(msg)
|
||||
case "tools/list":
|
||||
return s.handleToolsList(msg)
|
||||
case "tools/call":
|
||||
return s.handleToolsCall(msg)
|
||||
default:
|
||||
return s.errorResponse(msg.ID, -32601, "Method not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) handleInitialize(msg MCPMessage) MCPMessage {
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: msg.ID,
|
||||
Result: map[string]interface{}{
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": map[string]interface{}{
|
||||
"tools": map[string]interface{}{
|
||||
"listChanged": true,
|
||||
},
|
||||
},
|
||||
"serverInfo": map[string]interface{}{
|
||||
"name": s.config.Server.Name,
|
||||
"version": s.config.Server.Version,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) handleToolsList(msg MCPMessage) MCPMessage {
|
||||
toolsList := tools.GetMCPTools()
|
||||
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: msg.ID,
|
||||
Result: map[string]interface{}{
|
||||
"tools": toolsList,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) handleToolsCall(msg MCPMessage) MCPMessage {
|
||||
var params CallToolParams
|
||||
paramsBytes, _ := json.Marshal(msg.Params)
|
||||
json.Unmarshal(paramsBytes, ¶ms)
|
||||
|
||||
handlers := tools.GetToolHandlers(s)
|
||||
handler, exists := handlers[params.Name]
|
||||
|
||||
if !exists {
|
||||
return s.errorResponse(msg.ID, -32601, fmt.Sprintf("Unknown tool: %s", params.Name))
|
||||
}
|
||||
|
||||
result := handler(params.Arguments)
|
||||
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: msg.ID,
|
||||
Result: result,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MCPServer) errorResponse(id interface{}, code int, message string) MCPMessage {
|
||||
return MCPMessage{
|
||||
JSONRPC: "2.0",
|
||||
ID: id,
|
||||
Error: &MCPError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Tool implementations
|
||||
|
||||
func (s *MCPServer) ParseNginxConfig(args map[string]interface{}) tools.ToolResult {
|
||||
configContent, ok := args["config_content"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}}
|
||||
}
|
||||
|
||||
serverCount := strings.Count(configContent, "server {")
|
||||
locationCount := strings.Count(configContent, "location")
|
||||
hasSSL := strings.Contains(configContent, "ssl")
|
||||
hasProxy := strings.Contains(configContent, "proxy_pass")
|
||||
hasRewrite := strings.Contains(configContent, "rewrite")
|
||||
|
||||
complexity := "Simple"
|
||||
if serverCount > 1 || (hasRewrite && hasSSL) {
|
||||
complexity = "Complex"
|
||||
} else if hasRewrite || hasSSL {
|
||||
complexity = "Medium"
|
||||
}
|
||||
|
||||
analysis := fmt.Sprintf(`Nginx配置分析结果
|
||||
|
||||
基础信息:
|
||||
- Server块: %d个
|
||||
- Location块: %d个
|
||||
- SSL配置: %t
|
||||
- 反向代理: %t
|
||||
- URL重写: %t
|
||||
|
||||
复杂度: %s
|
||||
|
||||
迁移建议:`, serverCount, locationCount, hasSSL, hasProxy, hasRewrite, complexity)
|
||||
|
||||
if hasProxy {
|
||||
analysis += "\n- 反向代理将转换为Ingress backend配置"
|
||||
}
|
||||
if hasRewrite {
|
||||
analysis += "\n- URL重写将使用Higress注解 (higress.io/rewrite-target)"
|
||||
}
|
||||
if hasSSL {
|
||||
analysis += "\n- SSL配置将转换为Ingress TLS配置"
|
||||
}
|
||||
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: analysis}}}
|
||||
}
|
||||
|
||||
func (s *MCPServer) ConvertToHigress(args map[string]interface{}) tools.ToolResult {
|
||||
configContent, ok := args["config_content"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing config_content"}}}
|
||||
}
|
||||
|
||||
namespace := s.config.Defaults.Namespace
|
||||
if ns, ok := args["namespace"].(string); ok {
|
||||
namespace = ns
|
||||
}
|
||||
|
||||
// 检查是否使用 Gateway API
|
||||
useGatewayAPI := false
|
||||
if val, ok := args["use_gateway_api"].(bool); ok {
|
||||
useGatewayAPI = val
|
||||
}
|
||||
|
||||
// === 使用增强的解析器解析 Nginx 配置 ===
|
||||
nginxConfig, err := tools.ParseNginxConfig(configContent)
|
||||
if err != nil {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error parsing Nginx config: %v", err)}}}
|
||||
}
|
||||
|
||||
// 分析配置
|
||||
analysis := tools.AnalyzeNginxConfig(nginxConfig)
|
||||
|
||||
// === RAG 增强:查询转换示例和最佳实践 ===
|
||||
var ragContext string
|
||||
if s.ragManager != nil && s.ragManager.IsEnabled() {
|
||||
// 构建查询关键词
|
||||
queryBuilder := []string{"Nginx 配置转换到 Higress"}
|
||||
|
||||
if useGatewayAPI {
|
||||
queryBuilder = append(queryBuilder, "Gateway API HTTPRoute")
|
||||
} else {
|
||||
queryBuilder = append(queryBuilder, "Kubernetes Ingress")
|
||||
}
|
||||
|
||||
// 根据特性添加查询关键词
|
||||
if analysis.Features["ssl"] {
|
||||
queryBuilder = append(queryBuilder, "SSL TLS 证书配置")
|
||||
}
|
||||
if analysis.Features["rewrite"] {
|
||||
queryBuilder = append(queryBuilder, "URL 重写 rewrite 规则")
|
||||
}
|
||||
if analysis.Features["redirect"] {
|
||||
queryBuilder = append(queryBuilder, "重定向 redirect")
|
||||
}
|
||||
if analysis.Features["header_manipulation"] {
|
||||
queryBuilder = append(queryBuilder, "请求头 响应头处理")
|
||||
}
|
||||
if len(nginxConfig.Upstreams) > 0 {
|
||||
queryBuilder = append(queryBuilder, "负载均衡 upstream")
|
||||
}
|
||||
|
||||
queryString := strings.Join(queryBuilder, " ")
|
||||
log.Printf("RAG Query: %s", queryString)
|
||||
|
||||
ragResult, err := s.ragManager.QueryForTool(
|
||||
"convert_to_higress",
|
||||
queryString,
|
||||
"nginx_to_higress",
|
||||
)
|
||||
|
||||
if err == nil && ragResult.Enabled && len(ragResult.Documents) > 0 {
|
||||
log.Printf("RAG: Found %d documents for conversion", len(ragResult.Documents))
|
||||
ragContext = "\n\n## 参考文档(来自知识库)\n\n" + ragResult.FormatContextForAI()
|
||||
} else {
|
||||
if err != nil {
|
||||
log.Printf("WARNING: RAG query failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === 将配置数据转换为 JSON 供 AI 使用 ===
|
||||
configJSON, _ := json.MarshalIndent(nginxConfig, "", " ")
|
||||
analysisJSON, _ := json.MarshalIndent(analysis, "", " ")
|
||||
|
||||
// === 构建返回消息 ===
|
||||
userMessage := fmt.Sprintf(`📋 Nginx 配置解析完成
|
||||
|
||||
## 配置概览
|
||||
- Server 块: %d
|
||||
- Location 块: %d
|
||||
- 域名: %d 个
|
||||
- 复杂度: %s
|
||||
- 目标格式: %s
|
||||
- 命名空间: %s
|
||||
|
||||
## 检测到的特性
|
||||
%s
|
||||
|
||||
## 迁移建议
|
||||
%s
|
||||
%s
|
||||
|
||||
---
|
||||
|
||||
## Nginx 配置结构
|
||||
|
||||
`+"```json"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
## 分析结果
|
||||
|
||||
`+"```json"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
%s
|
||||
`,
|
||||
analysis.ServerCount,
|
||||
analysis.LocationCount,
|
||||
analysis.DomainCount,
|
||||
analysis.Complexity,
|
||||
func() string {
|
||||
if useGatewayAPI {
|
||||
return "Gateway API (HTTPRoute)"
|
||||
}
|
||||
return "Kubernetes Ingress"
|
||||
}(),
|
||||
namespace,
|
||||
formatFeatures(analysis.Features),
|
||||
formatSuggestions(analysis.Suggestions),
|
||||
func() string {
|
||||
if ragContext != "" {
|
||||
return "\n\n已加载知识库参考文档"
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
string(configJSON),
|
||||
string(analysisJSON),
|
||||
ragContext,
|
||||
)
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, "", map[string]interface{}{
|
||||
"nginx_config": nginxConfig,
|
||||
"analysis": analysis,
|
||||
"namespace": namespace,
|
||||
"use_gateway_api": useGatewayAPI,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MCPServer) AnalyzeLuaPlugin(args map[string]interface{}) tools.ToolResult {
|
||||
luaCode, ok := args["lua_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}}
|
||||
}
|
||||
|
||||
// 使用新的 AI 友好分析
|
||||
analysis := tools.AnalyzeLuaPluginForAI(luaCode)
|
||||
|
||||
// === RAG 增强:查询知识库获取转换建议 ===
|
||||
var ragContext string
|
||||
if s.ragManager != nil && s.ragManager.IsEnabled() && len(analysis.APICalls) > 0 {
|
||||
query := fmt.Sprintf("Nginx Lua API %s 在 Higress WASM 中的转换方法和最佳实践", strings.Join(analysis.APICalls, ", "))
|
||||
log.Printf("🔍 RAG Query: %s", query)
|
||||
|
||||
ragResult, err := s.ragManager.QueryForTool("analyze_lua_plugin", query, "lua_migration")
|
||||
if err == nil && ragResult.Enabled && len(ragResult.Documents) > 0 {
|
||||
log.Printf("RAG: Found %d documents for Lua analysis", len(ragResult.Documents))
|
||||
ragContext = "\n\n## 知识库参考资料\n\n" + ragResult.FormatContextForAI()
|
||||
} else if err != nil {
|
||||
log.Printf(" RAG query failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成用户友好的消息
|
||||
features := []string{}
|
||||
for feature := range analysis.Features {
|
||||
features = append(features, fmt.Sprintf("- %s", feature))
|
||||
}
|
||||
|
||||
userMessage := fmt.Sprintf(`Lua 插件分析完成
|
||||
|
||||
## 检测到的特性
|
||||
%s
|
||||
|
||||
## 基本信息
|
||||
- **复杂度**: %s
|
||||
- **兼容性**: %s
|
||||
|
||||
## 兼容性警告
|
||||
%s
|
||||
%s
|
||||
|
||||
## 后续操作
|
||||
- 调用 generate_conversion_hints 获取转换提示
|
||||
- 或直接使用 convert_lua_to_wasm 一键转换
|
||||
|
||||
## 分析结果
|
||||
|
||||
`+"```json"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
`,
|
||||
strings.Join(features, "\n"),
|
||||
analysis.Complexity,
|
||||
analysis.Compatibility,
|
||||
func() string {
|
||||
if len(analysis.Warnings) > 0 {
|
||||
return "- " + strings.Join(analysis.Warnings, "\n- ")
|
||||
}
|
||||
return "无"
|
||||
}(),
|
||||
ragContext,
|
||||
string(mustMarshalJSON(analysis)),
|
||||
)
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, "", analysis)
|
||||
}
|
||||
|
||||
func mustMarshalJSON(v interface{}) []byte {
|
||||
data, _ := json.Marshal(v)
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *MCPServer) ConvertLuaToWasm(args map[string]interface{}) tools.ToolResult {
|
||||
luaCode, ok := args["lua_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing lua_code"}}}
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
analyzer := tools.AnalyzeLuaScript(luaCode)
|
||||
result, err := tools.ConvertLuaToWasm(analyzer, pluginName)
|
||||
if err != nil {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error: %v", err)}}}
|
||||
}
|
||||
|
||||
response := fmt.Sprintf(`Lua脚本转换完成
|
||||
|
||||
转换分析:
|
||||
- 复杂度: %s
|
||||
- 检测特性: %d个
|
||||
- 兼容性警告: %d个
|
||||
|
||||
注意事项:
|
||||
%s
|
||||
|
||||
生成的文件:
|
||||
|
||||
==== main.go ====
|
||||
%s
|
||||
|
||||
==== WasmPlugin配置 ====
|
||||
%s
|
||||
|
||||
部署步骤:
|
||||
1. 创建插件目录: mkdir -p extensions/%s
|
||||
2. 保存Go代码到: extensions/%s/main.go
|
||||
3. 构建插件: PLUGIN_NAME=%s make build
|
||||
4. 应用配置: kubectl apply -f wasmplugin.yaml
|
||||
|
||||
提示:
|
||||
- 请根据实际需求调整配置
|
||||
- 测试插件功能后再部署到生产环境
|
||||
- 如有共享状态需求,请配置Redis等外部存储
|
||||
`,
|
||||
analyzer.Complexity,
|
||||
len(analyzer.Features),
|
||||
len(analyzer.Warnings),
|
||||
strings.Join(analyzer.Warnings, "\n- "),
|
||||
result.GoCode,
|
||||
result.WasmPluginYAML,
|
||||
pluginName, pluginName, pluginName)
|
||||
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: response}}}
|
||||
}
|
||||
|
||||
// GenerateConversionHints 生成详细的代码转换提示
|
||||
func (s *MCPServer) GenerateConversionHints(args map[string]interface{}) tools.ToolResult {
|
||||
analysisResultStr, ok := args["analysis_result"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing analysis_result"}}}
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
// 解析分析结果
|
||||
var analysis tools.AnalysisResultForAI
|
||||
if err := json.Unmarshal([]byte(analysisResultStr), &analysis); err != nil {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: fmt.Sprintf("Error parsing analysis_result: %v", err)}}}
|
||||
}
|
||||
|
||||
// 生成转换提示
|
||||
hints := tools.GenerateConversionHints(analysis, pluginName)
|
||||
|
||||
// === RAG 增强:查询 Nginx API 转换文档 ===
|
||||
var ragDocs string
|
||||
|
||||
// 构建更精确的查询语句
|
||||
queryBuilder := []string{}
|
||||
if len(analysis.APICalls) > 0 {
|
||||
queryBuilder = append(queryBuilder, "Nginx Lua API 转换到 Higress WASM")
|
||||
|
||||
// 针对不同的 API 类型使用不同的查询关键词
|
||||
hasHeaderOps := analysis.Features["header_manipulation"] || analysis.Features["request_headers"] || analysis.Features["response_headers"]
|
||||
hasBodyOps := analysis.Features["request_body"] || analysis.Features["response_body"]
|
||||
hasResponseControl := analysis.Features["response_control"]
|
||||
|
||||
if hasHeaderOps {
|
||||
queryBuilder = append(queryBuilder, "请求头和响应头处理")
|
||||
}
|
||||
if hasBodyOps {
|
||||
queryBuilder = append(queryBuilder, "请求体和响应体处理")
|
||||
}
|
||||
if hasResponseControl {
|
||||
queryBuilder = append(queryBuilder, "响应控制和状态码设置")
|
||||
}
|
||||
|
||||
// 添加具体的 API 调用
|
||||
if len(analysis.APICalls) > 0 && len(analysis.APICalls) <= 5 {
|
||||
queryBuilder = append(queryBuilder, fmt.Sprintf("涉及 API: %s", strings.Join(analysis.APICalls, ", ")))
|
||||
}
|
||||
} else {
|
||||
queryBuilder = append(queryBuilder, "Higress WASM 插件开发 基础示例 Go SDK 使用")
|
||||
}
|
||||
|
||||
// 添加复杂度相关的查询
|
||||
if analysis.Complexity == "high" {
|
||||
queryBuilder = append(queryBuilder, "复杂插件实现 高级功能")
|
||||
}
|
||||
|
||||
queryString := strings.Join(queryBuilder, " ")
|
||||
|
||||
// 只有当 RAG 启用时才查询
|
||||
if s.ragManager != nil && s.ragManager.IsEnabled() {
|
||||
log.Printf(" RAG Query: %s", queryString)
|
||||
|
||||
ragContext, err := s.ragManager.QueryForTool(
|
||||
"generate_conversion_hints",
|
||||
queryString,
|
||||
"lua_migration",
|
||||
)
|
||||
|
||||
if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
log.Printf("RAG: Found %d documents for conversion hints", len(ragContext.Documents))
|
||||
ragDocs = "\n\n## 参考文档(来自知识库)\n\n" + ragContext.FormatContextForAI()
|
||||
} else {
|
||||
if err != nil {
|
||||
log.Printf(" RAG query failed: %v", err)
|
||||
}
|
||||
ragDocs = ""
|
||||
}
|
||||
} else {
|
||||
ragDocs = ""
|
||||
}
|
||||
|
||||
// 格式化输出
|
||||
userMessage := fmt.Sprintf(` 代码转换提示
|
||||
|
||||
**插件名称**: %s
|
||||
**复杂度**: %s
|
||||
**兼容性**: %s
|
||||
%s
|
||||
|
||||
## 代码模板
|
||||
|
||||
%s
|
||||
%s
|
||||
`,
|
||||
pluginName,
|
||||
analysis.Complexity,
|
||||
analysis.Compatibility,
|
||||
func() string {
|
||||
if len(hints.Warnings) > 0 {
|
||||
return "\n**警告**: " + formatWarningsListForUser(hints.Warnings)
|
||||
}
|
||||
return ""
|
||||
}(),
|
||||
hints.CodeTemplate,
|
||||
ragDocs,
|
||||
)
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, "", hints)
|
||||
}
|
||||
|
||||
// ValidateWasmCode 验证生成的 Go WASM 代码
|
||||
func (s *MCPServer) ValidateWasmCode(args map[string]interface{}) tools.ToolResult {
|
||||
goCode, ok := args["go_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}}
|
||||
}
|
||||
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
// 执行验证
|
||||
report := tools.ValidateWasmCode(goCode, pluginName)
|
||||
|
||||
// 统计各类问题数量
|
||||
requiredCount := 0
|
||||
recommendedCount := 0
|
||||
optionalCount := 0
|
||||
bestPracticeCount := 0
|
||||
|
||||
for _, issue := range report.Issues {
|
||||
switch issue.Category {
|
||||
case "required":
|
||||
requiredCount++
|
||||
case "recommended":
|
||||
recommendedCount++
|
||||
case "optional":
|
||||
optionalCount++
|
||||
case "best_practice":
|
||||
bestPracticeCount++
|
||||
}
|
||||
}
|
||||
|
||||
// 构建用户消息
|
||||
userMessage := fmt.Sprintf(`## 代码验证报告
|
||||
|
||||
%s
|
||||
|
||||
### 发现的回调函数 (%d 个)
|
||||
%s
|
||||
|
||||
### 配置结构
|
||||
%s
|
||||
|
||||
### 问题分类
|
||||
|
||||
#### 必须修复 (%d 个)
|
||||
%s
|
||||
|
||||
#### 建议修复 (%d 个)
|
||||
%s
|
||||
|
||||
#### 可选优化 (%d 个)
|
||||
%s
|
||||
|
||||
#### 最佳实践 (%d 个)
|
||||
%s
|
||||
|
||||
### 缺失的导入包 (%d 个)
|
||||
%s
|
||||
|
||||
---
|
||||
|
||||
`,
|
||||
report.Summary,
|
||||
len(report.FoundCallbacks),
|
||||
formatCallbacksList(report.FoundCallbacks),
|
||||
formatConfigStatus(report.HasConfig),
|
||||
requiredCount,
|
||||
formatIssuesByCategory(report.Issues, "required"),
|
||||
recommendedCount,
|
||||
formatIssuesByCategory(report.Issues, "recommended"),
|
||||
optionalCount,
|
||||
formatIssuesByCategory(report.Issues, "optional"),
|
||||
bestPracticeCount,
|
||||
formatIssuesByCategory(report.Issues, "best_practice"),
|
||||
len(report.MissingImports),
|
||||
formatList(report.MissingImports),
|
||||
)
|
||||
|
||||
// === RAG 增强:查询最佳实践和代码规范 ===
|
||||
var ragBestPractices string
|
||||
|
||||
// 根据验证结果构建更针对性的查询
|
||||
queryBuilder := []string{"Higress WASM 插件"}
|
||||
|
||||
// 根据发现的问题类型添加关键词
|
||||
if requiredCount > 0 || recommendedCount > 0 {
|
||||
queryBuilder = append(queryBuilder, "常见错误")
|
||||
|
||||
// 检查具体问题类型
|
||||
for _, issue := range report.Issues {
|
||||
switch issue.Type {
|
||||
case "error_handling":
|
||||
queryBuilder = append(queryBuilder, "错误处理")
|
||||
case "api_usage":
|
||||
queryBuilder = append(queryBuilder, "API 使用规范")
|
||||
case "config":
|
||||
queryBuilder = append(queryBuilder, "配置解析")
|
||||
case "logging":
|
||||
queryBuilder = append(queryBuilder, "日志记录")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 代码已通过基础验证,查询优化建议
|
||||
queryBuilder = append(queryBuilder, "性能优化 最佳实践")
|
||||
}
|
||||
|
||||
// 根据回调函数类型添加特定查询
|
||||
for _, callback := range report.FoundCallbacks {
|
||||
if strings.Contains(callback, "RequestHeaders") {
|
||||
queryBuilder = append(queryBuilder, "请求头处理")
|
||||
}
|
||||
if strings.Contains(callback, "RequestBody") {
|
||||
queryBuilder = append(queryBuilder, "请求体处理")
|
||||
}
|
||||
if strings.Contains(callback, "ResponseHeaders") {
|
||||
queryBuilder = append(queryBuilder, "响应头处理")
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有缺失的导入,查询包管理相关信息
|
||||
if len(report.MissingImports) > 0 {
|
||||
queryBuilder = append(queryBuilder, "依赖包导入")
|
||||
}
|
||||
|
||||
queryString := strings.Join(queryBuilder, " ")
|
||||
|
||||
// 只有当 RAG 启用时才查询
|
||||
if s.ragManager != nil && s.ragManager.IsEnabled() {
|
||||
log.Printf("RAG Query: %s", queryString)
|
||||
|
||||
ragContext, err := s.ragManager.QueryForTool(
|
||||
"validate_wasm_code",
|
||||
queryString,
|
||||
"best_practice",
|
||||
)
|
||||
|
||||
if err == nil && ragContext.Enabled && len(ragContext.Documents) > 0 {
|
||||
log.Printf("RAG: Found %d best practice documents", len(ragContext.Documents))
|
||||
ragBestPractices = "\n\n### 最佳实践建议(来自知识库)\n\n" + ragContext.FormatContextForAI()
|
||||
userMessage += ragBestPractices
|
||||
} else {
|
||||
if err != nil {
|
||||
log.Printf(" RAG query failed for validation: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据问题级别给出建议
|
||||
hasRequired := requiredCount > 0
|
||||
if hasRequired {
|
||||
userMessage += "\n **请优先修复 \"必须修复\" 的问题**\n\n"
|
||||
} else if recommendedCount > 0 {
|
||||
userMessage += "\n **代码基本结构正确**,建议修复 \"建议修复\" 的问题\n\n"
|
||||
} else {
|
||||
userMessage += "\n **代码验证通过!** 可以调用 `generate_deployment_config` 生成部署配置\n\n"
|
||||
}
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, "", report)
|
||||
}
|
||||
|
||||
// GenerateDeploymentConfig 生成部署配置
|
||||
func (s *MCPServer) GenerateDeploymentConfig(args map[string]interface{}) tools.ToolResult {
|
||||
pluginName, ok := args["plugin_name"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing plugin_name"}}}
|
||||
}
|
||||
|
||||
goCode, ok := args["go_code"].(string)
|
||||
if !ok {
|
||||
return tools.ToolResult{Content: []tools.Content{{Type: "text", Text: "Error: Missing go_code"}}}
|
||||
}
|
||||
|
||||
namespace := "higress-system"
|
||||
if ns, ok := args["namespace"].(string); ok && ns != "" {
|
||||
namespace = ns
|
||||
}
|
||||
|
||||
configSchema := ""
|
||||
if cs, ok := args["config_schema"].(string); ok {
|
||||
configSchema = cs
|
||||
}
|
||||
|
||||
// 生成部署包
|
||||
pkg := tools.GenerateDeploymentPackage(pluginName, goCode, configSchema, namespace)
|
||||
|
||||
// 格式化输出
|
||||
userMessage := fmt.Sprintf(`🎉 部署配置生成完成!
|
||||
|
||||
插件 **%s** 的部署配置已生成(命名空间: %s)
|
||||
|
||||
## 生成的文件
|
||||
|
||||
1. **wasmplugin.yaml** - WasmPlugin 配置
|
||||
2. **Makefile** - 构建和部署脚本
|
||||
3. **Dockerfile** - 容器化打包
|
||||
4. **README.md** - 使用文档
|
||||
5. **test.sh** - 测试脚本
|
||||
|
||||
## 快速部署
|
||||
|
||||
`+"```bash"+`
|
||||
# 构建插件
|
||||
make build
|
||||
|
||||
# 构建并推送镜像
|
||||
make docker-build docker-push
|
||||
|
||||
# 部署
|
||||
make deploy
|
||||
|
||||
# 验证
|
||||
kubectl get wasmplugin -n %s
|
||||
`+"```"+`
|
||||
|
||||
## 配置文件
|
||||
|
||||
### wasmplugin.yaml
|
||||
`+"```yaml"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### Makefile
|
||||
`+"```makefile"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### Dockerfile
|
||||
`+"```dockerfile"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### README.md
|
||||
`+"```markdown"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
|
||||
### test.sh
|
||||
`+"```bash"+`
|
||||
%s
|
||||
`+"```"+`
|
||||
`,
|
||||
pluginName,
|
||||
namespace,
|
||||
namespace,
|
||||
pkg.WasmPluginYAML,
|
||||
pkg.Makefile,
|
||||
pkg.Dockerfile,
|
||||
pkg.README,
|
||||
pkg.TestScript,
|
||||
)
|
||||
|
||||
return tools.FormatToolResultWithAIContext(userMessage, "", pkg)
|
||||
}
|
||||
|
||||
// 辅助格式化函数
|
||||
|
||||
func formatWarningsListForUser(warnings []string) string {
|
||||
if len(warnings) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return strings.Join(warnings, "\n- ")
|
||||
}
|
||||
|
||||
func formatCallbacksList(callbacks []string) string {
|
||||
if len(callbacks) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return "- " + strings.Join(callbacks, "\n- ")
|
||||
}
|
||||
|
||||
func formatConfigStatus(hasConfig bool) string {
|
||||
if hasConfig {
|
||||
return " 已定义配置结构体"
|
||||
}
|
||||
return "- 未定义配置结构体(如不需要配置可忽略)"
|
||||
}
|
||||
|
||||
func formatIssuesByCategory(issues []tools.ValidationIssue, category string) string {
|
||||
var filtered []string
|
||||
for _, issue := range issues {
|
||||
if issue.Category == category {
|
||||
filtered = append(filtered, fmt.Sprintf("- **[%s]** %s\n 💡 建议: %s\n 📌 影响: %s",
|
||||
issue.Type, issue.Message, issue.Suggestion, issue.Impact))
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return strings.Join(filtered, "\n\n")
|
||||
}
|
||||
|
||||
func formatList(items []string) string {
|
||||
if len(items) == 0 {
|
||||
return "无"
|
||||
}
|
||||
return "- " + strings.Join(items, "\n- ")
|
||||
}
|
||||
|
||||
// formatFeatures 格式化特性列表
|
||||
func formatFeatures(features map[string]bool) string {
|
||||
featureNames := map[string]string{
|
||||
"ssl": "SSL/TLS 加密",
|
||||
"proxy": "反向代理",
|
||||
"rewrite": "URL 重写",
|
||||
"redirect": "重定向",
|
||||
"return": "返回指令",
|
||||
"complex_routing": "复杂路由匹配",
|
||||
"header_manipulation": "请求头操作",
|
||||
"response_headers": "响应头操作",
|
||||
}
|
||||
|
||||
var result []string
|
||||
for key, enabled := range features {
|
||||
if enabled {
|
||||
if name, ok := featureNames[key]; ok {
|
||||
result = append(result, fmt.Sprintf("- %s", name))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("- %s", key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return "- 基础配置(无特殊特性)"
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
// formatSuggestions 格式化建议列表
|
||||
func formatSuggestions(suggestions []string) string {
|
||||
if len(suggestions) == 0 {
|
||||
return "- 无特殊建议"
|
||||
}
|
||||
var result []string
|
||||
for _, s := range suggestions {
|
||||
result = append(result, fmt.Sprintf("- 💡 %s", s))
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Common types for nginx migration MCP server - Standalone Mode
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"nginx-migration-mcp/internal/rag"
|
||||
)
|
||||
|
||||
// MCPMessage represents a Model Context Protocol message structure
|
||||
type MCPMessage struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Params interface{} `json:"params,omitempty"`
|
||||
ID interface{} `json:"id,omitempty"`
|
||||
Result interface{} `json:"result,omitempty"`
|
||||
Error *MCPError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type MCPError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type CallToolParams struct {
|
||||
Name string `json:"name"`
|
||||
Arguments map[string]interface{} `json:"arguments,omitempty"`
|
||||
}
|
||||
|
||||
type MCPServer struct {
|
||||
config *ServerConfig
|
||||
ragManager *rag.RAGManager
|
||||
}
|
||||
|
||||
// MCPServer implements the tools.MCPServer interface
|
||||
// Method implementations are in server.go
|
||||
@@ -0,0 +1,405 @@
|
||||
// Lua to WASM conversion logic for Nginx migration
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// LuaAnalyzer analyzes Lua script features and generates conversion mappings
|
||||
type LuaAnalyzer struct {
|
||||
Features map[string]bool
|
||||
Variables map[string]string
|
||||
Functions []LuaFunction
|
||||
Warnings []string
|
||||
Complexity string
|
||||
}
|
||||
|
||||
type LuaFunction struct {
|
||||
Name string
|
||||
Body string
|
||||
Phase string // request_headers, request_body, response_headers, etc.
|
||||
}
|
||||
|
||||
// ConversionResult holds the generated WASM plugin code
|
||||
type ConversionResult struct {
|
||||
PluginName string
|
||||
GoCode string
|
||||
ConfigSchema string
|
||||
Dependencies []string
|
||||
WasmPluginYAML string
|
||||
}
|
||||
|
||||
// AnalyzeLuaScript performs detailed analysis of Lua script
|
||||
func AnalyzeLuaScript(luaCode string) *LuaAnalyzer {
|
||||
analyzer := &LuaAnalyzer{
|
||||
Features: make(map[string]bool),
|
||||
Variables: make(map[string]string),
|
||||
Functions: []LuaFunction{},
|
||||
Warnings: []string{},
|
||||
Complexity: "simple",
|
||||
}
|
||||
|
||||
lines := strings.Split(luaCode, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "--") {
|
||||
continue
|
||||
}
|
||||
|
||||
// 分析ngx变量使用
|
||||
analyzer.analyzeNginxVars(line)
|
||||
|
||||
// 分析API调用
|
||||
analyzer.analyzeAPICalls(line)
|
||||
|
||||
// 分析函数定义
|
||||
analyzer.analyzeFunctions(line, luaCode)
|
||||
}
|
||||
|
||||
// 根据特性确定复杂度
|
||||
analyzer.determineComplexity()
|
||||
|
||||
return analyzer
|
||||
}
|
||||
|
||||
func (la *LuaAnalyzer) analyzeNginxVars(line string) {
|
||||
// 匹配 ngx.var.xxx 模式
|
||||
varPattern := regexp.MustCompile(`ngx\.var\.(\w+)`)
|
||||
matches := varPattern.FindAllStringSubmatch(line, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
if len(match) > 1 {
|
||||
varName := match[1]
|
||||
la.Features["ngx.var"] = true
|
||||
|
||||
// 映射常见变量到WASM等价物
|
||||
switch varName {
|
||||
case "uri":
|
||||
la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":path\")"
|
||||
case "request_method":
|
||||
la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":method\")"
|
||||
case "host":
|
||||
la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":authority\")"
|
||||
case "remote_addr":
|
||||
la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\"x-forwarded-for\")"
|
||||
case "request_uri":
|
||||
la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":path\")"
|
||||
case "scheme":
|
||||
la.Variables[varName] = "proxywasm.GetHttpRequestHeader(\":scheme\")"
|
||||
default:
|
||||
la.Variables[varName] = fmt.Sprintf("proxywasm.GetHttpRequestHeader(\"%s\")", varName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (la *LuaAnalyzer) analyzeAPICalls(line string) {
|
||||
apiCalls := map[string]string{
|
||||
"ngx.req.get_headers": "request_headers",
|
||||
"ngx.req.get_body_data": "request_body",
|
||||
"ngx.req.read_body": "request_body",
|
||||
"ngx.exit": "response_control",
|
||||
"ngx.say": "response_control",
|
||||
"ngx.print": "response_control",
|
||||
"ngx.shared": "shared_dict",
|
||||
"ngx.location.capture": "internal_request",
|
||||
"ngx.req.set_header": "header_manipulation",
|
||||
"ngx.header": "response_headers",
|
||||
}
|
||||
|
||||
for apiCall, feature := range apiCalls {
|
||||
if strings.Contains(line, apiCall) {
|
||||
la.Features[feature] = true
|
||||
|
||||
// 添加特定警告
|
||||
switch feature {
|
||||
case "shared_dict":
|
||||
la.Warnings = append(la.Warnings, "共享字典需要使用Redis或其他外部缓存替代")
|
||||
case "internal_request":
|
||||
la.Warnings = append(la.Warnings, "内部请求需要改为HTTP客户端调用")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (la *LuaAnalyzer) analyzeFunctions(line string, fullCode string) {
|
||||
// 检测函数定义
|
||||
funcPattern := regexp.MustCompile(`function\s+(\w+)\s*\(`)
|
||||
matches := funcPattern.FindAllStringSubmatch(line, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
if len(match) > 1 {
|
||||
funcName := match[1]
|
||||
|
||||
// 提取函数体 (简化实现)
|
||||
funcBody := la.extractFunctionBody(fullCode, funcName)
|
||||
|
||||
// 根据函数名推断执行阶段
|
||||
phase := "request_headers"
|
||||
if strings.Contains(funcName, "body") {
|
||||
phase = "request_body"
|
||||
} else if strings.Contains(funcName, "response") {
|
||||
phase = "response_headers"
|
||||
}
|
||||
|
||||
la.Functions = append(la.Functions, LuaFunction{
|
||||
Name: funcName,
|
||||
Body: funcBody,
|
||||
Phase: phase,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (la *LuaAnalyzer) extractFunctionBody(fullCode, funcName string) string {
|
||||
// 简化的函数体提取 - 实际实现应该更复杂
|
||||
pattern := fmt.Sprintf(`function\s+%s\s*\([^)]*\)(.*?)end`, funcName)
|
||||
re := regexp.MustCompile(pattern)
|
||||
match := re.FindStringSubmatch(fullCode)
|
||||
if len(match) > 1 {
|
||||
return strings.TrimSpace(match[1])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (la *LuaAnalyzer) determineComplexity() {
|
||||
warningCount := len(la.Warnings)
|
||||
featureCount := len(la.Features)
|
||||
|
||||
if warningCount > 3 || featureCount > 6 {
|
||||
la.Complexity = "complex"
|
||||
} else if warningCount > 1 || featureCount > 3 {
|
||||
la.Complexity = "medium"
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertLuaToWasm converts analyzed Lua script to WASM plugin
|
||||
func ConvertLuaToWasm(analyzer *LuaAnalyzer, pluginName string) (*ConversionResult, error) {
|
||||
result := &ConversionResult{
|
||||
PluginName: pluginName,
|
||||
Dependencies: []string{
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm",
|
||||
"github.com/higress-group/wasm-go/pkg/wrapper",
|
||||
"github.com/higress-group/wasm-go/pkg/log",
|
||||
},
|
||||
}
|
||||
|
||||
// 生成Go代码
|
||||
goCode, err := generateGoCode(analyzer, pluginName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.GoCode = goCode
|
||||
|
||||
// 生成配置模式
|
||||
result.ConfigSchema = generateConfigSchema(analyzer)
|
||||
|
||||
// 生成WasmPlugin YAML
|
||||
result.WasmPluginYAML = generateWasmPluginYAML(pluginName)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func generateGoCode(analyzer *LuaAnalyzer, pluginName string) (string, error) {
|
||||
tmpl := `// Generated WASM plugin from Lua script
|
||||
// Plugin: {{.PluginName}}
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/higress-group/wasm-go/pkg/log"
|
||||
"github.com/higress-group/wasm-go/pkg/wrapper"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
func init() {
|
||||
wrapper.SetCtx(
|
||||
"{{.PluginName}}",
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
{{- if .HasRequestHeaders}}
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
|
||||
{{- end}}
|
||||
{{- if .HasRequestBody}}
|
||||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||||
{{- end}}
|
||||
{{- if .HasResponseHeaders}}
|
||||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
|
||||
{{- end}}
|
||||
)
|
||||
}
|
||||
|
||||
type {{.ConfigTypeName}} struct {
|
||||
// Generated from Lua analysis
|
||||
{{- range .ConfigFields}}
|
||||
{{.Name}} {{.Type}} ` + "`json:\"{{.JSONName}}\"`" + `
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, config *{{.ConfigTypeName}}, log log.Log) error {
|
||||
{{- range .ConfigFields}}
|
||||
config.{{.Name}} = json.Get("{{.JSONName}}").{{.ParseMethod}}()
|
||||
{{- end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
{{- if .HasRequestHeaders}}
|
||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, config {{.ConfigTypeName}}, log log.Log) types.Action {
|
||||
{{.RequestHeadersLogic}}
|
||||
return types.ActionContinue
|
||||
}
|
||||
{{- end}}
|
||||
|
||||
{{- if .HasRequestBody}}
|
||||
func onHttpRequestBody(ctx wrapper.HttpContext, config {{.ConfigTypeName}}, body []byte, log log.Log) types.Action {
|
||||
{{.RequestBodyLogic}}
|
||||
return types.ActionContinue
|
||||
}
|
||||
{{- end}}
|
||||
|
||||
{{- if .HasResponseHeaders}}
|
||||
func onHttpResponseHeaders(ctx wrapper.HttpContext, config {{.ConfigTypeName}}, log log.Log) types.Action {
|
||||
{{.ResponseHeadersLogic}}
|
||||
return types.ActionContinue
|
||||
}
|
||||
{{- end}}
|
||||
`
|
||||
|
||||
// 准备模板数据
|
||||
data := map[string]interface{}{
|
||||
"PluginName": pluginName,
|
||||
"ConfigTypeName": strings.Title(pluginName) + "Config",
|
||||
"HasRequestHeaders": analyzer.Features["request_headers"] || analyzer.Features["ngx.var"],
|
||||
"HasRequestBody": analyzer.Features["request_body"],
|
||||
"HasResponseHeaders": analyzer.Features["response_headers"] || analyzer.Features["response_control"],
|
||||
"ConfigFields": generateConfigFields(analyzer),
|
||||
"RequestHeadersLogic": generateRequestHeadersLogic(analyzer),
|
||||
"RequestBodyLogic": generateRequestBodyLogic(analyzer),
|
||||
"ResponseHeadersLogic": generateResponseHeadersLogic(analyzer),
|
||||
}
|
||||
|
||||
t, err := template.New("wasm").Parse(tmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var buf strings.Builder
|
||||
err = t.Execute(&buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func generateConfigFields(analyzer *LuaAnalyzer) []map[string]string {
|
||||
fields := []map[string]string{}
|
||||
|
||||
// 基于分析的特性生成配置字段
|
||||
if analyzer.Features["response_control"] {
|
||||
fields = append(fields, map[string]string{
|
||||
"Name": "EnableCustomResponse",
|
||||
"Type": "bool",
|
||||
"JSONName": "enable_custom_response",
|
||||
"ParseMethod": "Bool",
|
||||
})
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func generateRequestHeadersLogic(analyzer *LuaAnalyzer) string {
|
||||
logic := []string{}
|
||||
|
||||
// 基于变量使用生成逻辑
|
||||
for varName, wasmCall := range analyzer.Variables {
|
||||
logic = append(logic, fmt.Sprintf(`
|
||||
// Access to ngx.var.%s
|
||||
%s, err := %s
|
||||
if err != nil {
|
||||
log.Warnf("Failed to get %s: %%v", err)
|
||||
}`, varName, varName, wasmCall, varName))
|
||||
}
|
||||
|
||||
if analyzer.Features["header_manipulation"] {
|
||||
logic = append(logic, `
|
||||
// Header manipulation logic
|
||||
err := proxywasm.AddHttpRequestHeader("x-converted-from", "nginx-lua")
|
||||
if err != nil {
|
||||
log.Warnf("Failed to add header: %v", err)
|
||||
}`)
|
||||
}
|
||||
|
||||
return strings.Join(logic, "\n")
|
||||
}
|
||||
|
||||
func generateRequestBodyLogic(analyzer *LuaAnalyzer) string {
|
||||
if analyzer.Features["request_body"] {
|
||||
return `
|
||||
// Process request body
|
||||
bodyStr := string(body)
|
||||
log.Infof("Processing request body: %s", bodyStr)
|
||||
|
||||
// Add your body processing logic here
|
||||
`
|
||||
}
|
||||
return "// No request body processing needed"
|
||||
}
|
||||
|
||||
func generateResponseHeadersLogic(analyzer *LuaAnalyzer) string {
|
||||
if analyzer.Features["response_control"] {
|
||||
return `
|
||||
// Response control logic
|
||||
if config.EnableCustomResponse {
|
||||
proxywasm.SendHttpResponseWithDetail(200, "lua-converted", nil, []byte("Response from converted Lua plugin"), -1)
|
||||
return types.ActionContinue
|
||||
}
|
||||
`
|
||||
}
|
||||
return "// No response processing needed"
|
||||
}
|
||||
|
||||
func generateConfigSchema(analyzer *LuaAnalyzer) string {
|
||||
schema := `{
|
||||
"type": "object",
|
||||
"properties": {`
|
||||
|
||||
properties := []string{}
|
||||
|
||||
if analyzer.Features["response_control"] {
|
||||
properties = append(properties, `
|
||||
"enable_custom_response": {
|
||||
"type": "boolean",
|
||||
"description": "Enable custom response handling"
|
||||
}`)
|
||||
}
|
||||
|
||||
schema += strings.Join(properties, ",")
|
||||
schema += `
|
||||
}
|
||||
}`
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
func generateWasmPluginYAML(pluginName string) string {
|
||||
return fmt.Sprintf(`apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: %s
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfig:
|
||||
enable_custom_response: true
|
||||
url: oci://your-registry/%s:latest
|
||||
`, pluginName, pluginName)
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// MCP Tools Definitions
|
||||
// 定义所有可用的MCP工具及其描述信息
|
||||
package tools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
)
|
||||
|
||||
// MCPTool represents a tool definition in MCP protocol
|
||||
type MCPTool struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
}
|
||||
|
||||
// ToolResult represents the result of a tool call
|
||||
type ToolResult struct {
|
||||
Content []Content `json:"content"`
|
||||
}
|
||||
|
||||
// Content represents content within a tool result
|
||||
type Content struct {
|
||||
Type string `json:"type"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// MCPServer is an interface for server methods needed by tool handlers
|
||||
type MCPServer interface {
|
||||
ParseNginxConfig(args map[string]interface{}) ToolResult
|
||||
ConvertToHigress(args map[string]interface{}) ToolResult
|
||||
AnalyzeLuaPlugin(args map[string]interface{}) ToolResult
|
||||
ConvertLuaToWasm(args map[string]interface{}) ToolResult
|
||||
// 新增工具链方法
|
||||
GenerateConversionHints(args map[string]interface{}) ToolResult
|
||||
ValidateWasmCode(args map[string]interface{}) ToolResult
|
||||
GenerateDeploymentConfig(args map[string]interface{}) ToolResult
|
||||
}
|
||||
|
||||
// MCPToolsConfig 工具配置文件结构
|
||||
type MCPToolsConfig struct {
|
||||
Version string `json:"version"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Tools []MCPTool `json:"tools"`
|
||||
}
|
||||
|
||||
// LoadToolsFromFile 从 JSON 文件加载工具定义
|
||||
func LoadToolsFromFile(filename string) ([]MCPTool, error) {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
// 文件不存在时使用默认配置
|
||||
return GetMCPToolsDefault(), nil
|
||||
}
|
||||
|
||||
var config MCPToolsConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config.Tools, nil
|
||||
}
|
||||
|
||||
// GetMCPTools 返回所有可用的 MCP 工具定义
|
||||
// 优先从 mcp-tools.json 加载,失败时使用默认定义
|
||||
func GetMCPTools() []MCPTool {
|
||||
tools, err := LoadToolsFromFile("mcp-tools.json")
|
||||
if err != nil {
|
||||
return GetMCPToolsDefault()
|
||||
}
|
||||
return tools
|
||||
}
|
||||
|
||||
// GetMCPToolsDefault 返回默认的工具定义(包含完整工具链)
|
||||
func GetMCPToolsDefault() []MCPTool {
|
||||
return []MCPTool{
|
||||
{
|
||||
Name: "parse_nginx_config",
|
||||
Description: "解析和分析 Nginx 配置文件,识别配置结构和复杂度",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config_content": {
|
||||
"type": "string",
|
||||
"description": "Nginx 配置文件内容"
|
||||
}
|
||||
},
|
||||
"required": ["config_content"]
|
||||
}`),
|
||||
},
|
||||
{
|
||||
Name: "convert_to_higress",
|
||||
Description: "将 Nginx 配置转换为 Higress HTTPRoute 和 Service 资源",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"config_content": {
|
||||
"type": "string",
|
||||
"description": "Nginx 配置文件内容"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "目标 Kubernetes 命名空间",
|
||||
"default": "default"
|
||||
}
|
||||
},
|
||||
"required": ["config_content"]
|
||||
}`),
|
||||
},
|
||||
{
|
||||
Name: "analyze_lua_plugin",
|
||||
Description: "分析 Nginx Lua 插件的兼容性,识别使用的 API 和潜在迁移问题,返回结构化分析结果",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lua_code": {
|
||||
"type": "string",
|
||||
"description": "Nginx Lua 插件代码"
|
||||
}
|
||||
},
|
||||
"required": ["lua_code"]
|
||||
}`),
|
||||
},
|
||||
{
|
||||
Name: "generate_conversion_hints",
|
||||
Description: "基于 Lua 分析结果生成代码转换模板",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"analysis_result": {
|
||||
"type": "string",
|
||||
"description": "analyze_lua_plugin 返回的 JSON 格式分析结果"
|
||||
},
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "目标插件名称(小写字母和连字符)"
|
||||
}
|
||||
},
|
||||
"required": ["analysis_result", "plugin_name"]
|
||||
}`),
|
||||
},
|
||||
{
|
||||
Name: "validate_wasm_code",
|
||||
Description: "验证生成的 Go WASM 插件代码,检查语法、API 使用、配置结构等,输出验证报告和改进建议",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"go_code": {
|
||||
"type": "string",
|
||||
"description": "生成的 Go WASM 插件代码"
|
||||
},
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "插件名称"
|
||||
}
|
||||
},
|
||||
"required": ["go_code", "plugin_name"]
|
||||
}`),
|
||||
},
|
||||
{
|
||||
Name: "generate_deployment_config",
|
||||
Description: "为验证通过的 WASM 插件生成完整的部署配置包,包括 WasmPlugin YAML、Makefile、Dockerfile、README 和测试脚本",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "插件名称"
|
||||
},
|
||||
"go_code": {
|
||||
"type": "string",
|
||||
"description": "验证通过的 Go 代码"
|
||||
},
|
||||
"config_schema": {
|
||||
"type": "string",
|
||||
"description": "配置 JSON Schema(可选)"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "string",
|
||||
"description": "部署命名空间",
|
||||
"default": "higress-system"
|
||||
}
|
||||
},
|
||||
"required": ["plugin_name", "go_code"]
|
||||
}`),
|
||||
},
|
||||
{
|
||||
Name: "convert_lua_to_wasm",
|
||||
Description: "一键将 Nginx Lua 脚本转换为 Higress WASM 插件,自动生成 Go 代码和 WasmPlugin 配置。适合简单插件快速转换",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lua_code": {
|
||||
"type": "string",
|
||||
"description": "要转换的 Nginx Lua 插件代码"
|
||||
},
|
||||
"plugin_name": {
|
||||
"type": "string",
|
||||
"description": "生成的 WASM 插件名称 (小写字母和连字符)"
|
||||
}
|
||||
},
|
||||
"required": ["lua_code", "plugin_name"]
|
||||
}`),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ToolHandler 定义工具处理函数的类型
|
||||
type ToolHandler func(args map[string]interface{}) ToolResult
|
||||
|
||||
// GetToolHandlers 返回工具名称到处理函数的映射
|
||||
func GetToolHandlers(s MCPServer) map[string]ToolHandler {
|
||||
return map[string]ToolHandler{
|
||||
"parse_nginx_config": s.ParseNginxConfig,
|
||||
"convert_to_higress": s.ConvertToHigress,
|
||||
"analyze_lua_plugin": s.AnalyzeLuaPlugin,
|
||||
"convert_lua_to_wasm": s.ConvertLuaToWasm,
|
||||
// 新增工具链处理器
|
||||
"generate_conversion_hints": s.GenerateConversionHints,
|
||||
"validate_wasm_code": s.ValidateWasmCode,
|
||||
"generate_deployment_config": s.GenerateDeploymentConfig,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
// Package tools provides Nginx configuration parsing and analysis capabilities.
|
||||
// This intelligent parser extracts semantic information from Nginx configs for AI reasoning.
|
||||
package tools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NginxConfig 表示解析后的 Nginx 配置结构
|
||||
type NginxConfig struct {
|
||||
Servers []NginxServer `json:"servers"`
|
||||
Upstreams []NginxUpstream `json:"upstreams"`
|
||||
Raw string `json:"raw"`
|
||||
}
|
||||
|
||||
// NginxServer 表示一个 server 块
|
||||
type NginxServer struct {
|
||||
Listen []string `json:"listen"` // 监听端口和地址
|
||||
ServerNames []string `json:"server_names"` // 域名列表
|
||||
Locations []NginxLocation `json:"locations"` // location 块列表
|
||||
SSL *NginxSSL `json:"ssl,omitempty"` // SSL 配置
|
||||
Directives map[string][]string `json:"directives"` // 其他指令
|
||||
}
|
||||
|
||||
// NginxLocation 表示一个 location 块
|
||||
type NginxLocation struct {
|
||||
Path string `json:"path"` // 路径
|
||||
Modifier string `json:"modifier"` // 修饰符(=, ~, ~*, ^~)
|
||||
ProxyPass string `json:"proxy_pass,omitempty"` // 代理目标
|
||||
Rewrite []string `json:"rewrite,omitempty"` // rewrite 规则
|
||||
Return *NginxReturn `json:"return,omitempty"` // return 指令
|
||||
Directives map[string][]string `json:"directives"` // 其他指令
|
||||
}
|
||||
|
||||
// NginxSSL 表示 SSL 配置
|
||||
type NginxSSL struct {
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
CertificateKey string `json:"certificate_key,omitempty"`
|
||||
Protocols []string `json:"protocols,omitempty"`
|
||||
Ciphers string `json:"ciphers,omitempty"`
|
||||
}
|
||||
|
||||
// NginxReturn 表示 return 指令
|
||||
type NginxReturn struct {
|
||||
Code int `json:"code"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
// NginxUpstream 表示 upstream 块
|
||||
type NginxUpstream struct {
|
||||
Name string `json:"name"`
|
||||
Servers []string `json:"servers"`
|
||||
Method string `json:"method,omitempty"` // 负载均衡方法
|
||||
}
|
||||
|
||||
// ParseNginxConfig 解析 Nginx 配置内容
|
||||
func ParseNginxConfig(content string) (*NginxConfig, error) {
|
||||
config := &NginxConfig{
|
||||
Raw: content,
|
||||
Servers: []NginxServer{},
|
||||
Upstreams: []NginxUpstream{},
|
||||
}
|
||||
|
||||
// 解析 upstream 块
|
||||
upstreams := extractUpstreams(content)
|
||||
config.Upstreams = upstreams
|
||||
|
||||
// 解析 server 块
|
||||
servers := extractServers(content)
|
||||
for _, serverContent := range servers {
|
||||
server := parseServer(serverContent)
|
||||
config.Servers = append(config.Servers, server)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// extractUpstreams 提取所有 upstream 块
|
||||
func extractUpstreams(content string) []NginxUpstream {
|
||||
upstreams := []NginxUpstream{}
|
||||
upstreamRegex := regexp.MustCompile(`upstream\s+(\S+)\s*\{([^}]*)\}`)
|
||||
matches := upstreamRegex.FindAllStringSubmatch(content, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
if len(match) >= 3 {
|
||||
name := match[1]
|
||||
body := match[2]
|
||||
upstream := NginxUpstream{
|
||||
Name: name,
|
||||
Servers: []string{},
|
||||
}
|
||||
|
||||
// 提取 server 指令
|
||||
serverRegex := regexp.MustCompile(`server\s+([^;]+);`)
|
||||
serverMatches := serverRegex.FindAllStringSubmatch(body, -1)
|
||||
for _, sm := range serverMatches {
|
||||
if len(sm) >= 2 {
|
||||
upstream.Servers = append(upstream.Servers, strings.TrimSpace(sm[1]))
|
||||
}
|
||||
}
|
||||
|
||||
// 检测负载均衡方法
|
||||
if strings.Contains(body, "ip_hash") {
|
||||
upstream.Method = "ip_hash"
|
||||
} else if strings.Contains(body, "least_conn") {
|
||||
upstream.Method = "least_conn"
|
||||
}
|
||||
|
||||
upstreams = append(upstreams, upstream)
|
||||
}
|
||||
}
|
||||
|
||||
return upstreams
|
||||
}
|
||||
|
||||
// extractServers 提取所有 server 块的内容
|
||||
func extractServers(content string) []string {
|
||||
servers := []string{}
|
||||
|
||||
// 简单的大括号匹配提取
|
||||
lines := strings.Split(content, "\n")
|
||||
inServer := false
|
||||
braceCount := 0
|
||||
var currentServer strings.Builder
|
||||
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
|
||||
// 检测 server 块开始
|
||||
if strings.HasPrefix(trimmed, "server") && strings.Contains(trimmed, "{") {
|
||||
inServer = true
|
||||
currentServer.Reset()
|
||||
currentServer.WriteString(line + "\n")
|
||||
braceCount = strings.Count(line, "{") - strings.Count(line, "}")
|
||||
continue
|
||||
}
|
||||
|
||||
if inServer {
|
||||
currentServer.WriteString(line + "\n")
|
||||
braceCount += strings.Count(line, "{") - strings.Count(line, "}")
|
||||
|
||||
if braceCount == 0 {
|
||||
servers = append(servers, currentServer.String())
|
||||
inServer = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
// parseServer 解析单个 server 块
|
||||
func parseServer(content string) NginxServer {
|
||||
server := NginxServer{
|
||||
Listen: []string{},
|
||||
ServerNames: []string{},
|
||||
Locations: []NginxLocation{},
|
||||
Directives: make(map[string][]string),
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
// 解析 listen 指令
|
||||
listenRegex := regexp.MustCompile(`^\s*listen\s+([^;]+);`)
|
||||
for _, line := range lines {
|
||||
if match := listenRegex.FindStringSubmatch(line); match != nil {
|
||||
server.Listen = append(server.Listen, strings.TrimSpace(match[1]))
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 server_name 指令
|
||||
serverNameRegex := regexp.MustCompile(`^\s*server_name\s+([^;]+);`)
|
||||
for _, line := range lines {
|
||||
if match := serverNameRegex.FindStringSubmatch(line); match != nil {
|
||||
names := strings.Fields(match[1])
|
||||
server.ServerNames = append(server.ServerNames, names...)
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 SSL 配置
|
||||
server.SSL = parseSSL(content)
|
||||
|
||||
// 解析 location 块
|
||||
server.Locations = extractLocations(content)
|
||||
|
||||
// 解析其他常见指令
|
||||
commonDirectives := []string{
|
||||
"root", "index", "access_log", "error_log",
|
||||
"client_max_body_size", "proxy_set_header",
|
||||
}
|
||||
|
||||
for _, directive := range commonDirectives {
|
||||
pattern := fmt.Sprintf(`(?m)^\s*%s\s+([^;]+);`, directive)
|
||||
regex := regexp.MustCompile(pattern)
|
||||
matches := regex.FindAllStringSubmatch(content, -1)
|
||||
for _, match := range matches {
|
||||
if len(match) >= 2 {
|
||||
server.Directives[directive] = append(server.Directives[directive], strings.TrimSpace(match[1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
// parseSSL 解析 SSL 配置
|
||||
func parseSSL(content string) *NginxSSL {
|
||||
hasSSL := strings.Contains(content, "ssl") || strings.Contains(content, "443")
|
||||
if !hasSSL {
|
||||
return nil
|
||||
}
|
||||
|
||||
ssl := &NginxSSL{
|
||||
Protocols: []string{},
|
||||
}
|
||||
|
||||
// 提取证书路径
|
||||
certRegex := regexp.MustCompile(`ssl_certificate\s+([^;]+);`)
|
||||
if match := certRegex.FindStringSubmatch(content); match != nil {
|
||||
ssl.Certificate = strings.TrimSpace(match[1])
|
||||
}
|
||||
|
||||
// 提取私钥路径
|
||||
keyRegex := regexp.MustCompile(`ssl_certificate_key\s+([^;]+);`)
|
||||
if match := keyRegex.FindStringSubmatch(content); match != nil {
|
||||
ssl.CertificateKey = strings.TrimSpace(match[1])
|
||||
}
|
||||
|
||||
// 提取协议
|
||||
protocolRegex := regexp.MustCompile(`ssl_protocols\s+([^;]+);`)
|
||||
if match := protocolRegex.FindStringSubmatch(content); match != nil {
|
||||
ssl.Protocols = strings.Fields(match[1])
|
||||
}
|
||||
|
||||
// 提取加密套件
|
||||
cipherRegex := regexp.MustCompile(`ssl_ciphers\s+([^;]+);`)
|
||||
if match := cipherRegex.FindStringSubmatch(content); match != nil {
|
||||
ssl.Ciphers = strings.TrimSpace(match[1])
|
||||
}
|
||||
|
||||
return ssl
|
||||
}
|
||||
|
||||
// extractLocations 提取所有 location 块
|
||||
func extractLocations(content string) []NginxLocation {
|
||||
locations := []NginxLocation{}
|
||||
|
||||
// 匹配 location 块
|
||||
locationRegex := regexp.MustCompile(`location\s+(=|~|~\*|\^~)?\s*([^\s{]+)\s*\{([^}]*)\}`)
|
||||
matches := locationRegex.FindAllStringSubmatch(content, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
if len(match) >= 4 {
|
||||
modifier := strings.TrimSpace(match[1])
|
||||
path := strings.TrimSpace(match[2])
|
||||
body := match[3]
|
||||
|
||||
location := NginxLocation{
|
||||
Path: path,
|
||||
Modifier: modifier,
|
||||
Rewrite: []string{},
|
||||
Directives: make(map[string][]string),
|
||||
}
|
||||
|
||||
// 提取 proxy_pass
|
||||
proxyPassRegex := regexp.MustCompile(`proxy_pass\s+([^;]+);`)
|
||||
if ppMatch := proxyPassRegex.FindStringSubmatch(body); ppMatch != nil {
|
||||
location.ProxyPass = strings.TrimSpace(ppMatch[1])
|
||||
}
|
||||
|
||||
// 提取 rewrite 规则
|
||||
rewriteRegex := regexp.MustCompile(`rewrite\s+([^;]+);`)
|
||||
rewriteMatches := rewriteRegex.FindAllStringSubmatch(body, -1)
|
||||
for _, rm := range rewriteMatches {
|
||||
if len(rm) >= 2 {
|
||||
location.Rewrite = append(location.Rewrite, strings.TrimSpace(rm[1]))
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 return 指令
|
||||
returnRegex := regexp.MustCompile(`return\s+(\d+)(?:\s+([^;]+))?;`)
|
||||
if retMatch := returnRegex.FindStringSubmatch(body); retMatch != nil {
|
||||
code := 0
|
||||
fmt.Sscanf(retMatch[1], "%d", &code)
|
||||
location.Return = &NginxReturn{
|
||||
Code: code,
|
||||
}
|
||||
if len(retMatch) >= 3 {
|
||||
urlOrText := strings.TrimSpace(retMatch[2])
|
||||
if strings.HasPrefix(urlOrText, "http") {
|
||||
location.Return.URL = urlOrText
|
||||
} else {
|
||||
location.Return.Text = urlOrText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提取其他指令
|
||||
commonDirectives := []string{
|
||||
"proxy_set_header", "proxy_redirect", "proxy_read_timeout",
|
||||
"add_header", "alias", "root", "try_files",
|
||||
}
|
||||
|
||||
for _, directive := range commonDirectives {
|
||||
pattern := fmt.Sprintf(`(?m)^\s*%s\s+([^;]+);`, directive)
|
||||
regex := regexp.MustCompile(pattern)
|
||||
matches := regex.FindAllStringSubmatch(body, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) >= 2 {
|
||||
location.Directives[directive] = append(location.Directives[directive], strings.TrimSpace(m[1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locations = append(locations, location)
|
||||
}
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
// AnalyzeNginxConfig 分析 Nginx 配置,生成用于 AI 的分析报告
|
||||
func AnalyzeNginxConfig(config *NginxConfig) *NginxAnalysis {
|
||||
analysis := &NginxAnalysis{
|
||||
ServerCount: len(config.Servers),
|
||||
Features: make(map[string]bool),
|
||||
Complexity: "simple",
|
||||
Suggestions: []string{},
|
||||
}
|
||||
|
||||
totalLocations := 0
|
||||
hasSSL := false
|
||||
hasRewrite := false
|
||||
hasUpstream := len(config.Upstreams) > 0
|
||||
hasComplexRouting := false
|
||||
uniqueDomains := make(map[string]bool)
|
||||
|
||||
for _, server := range config.Servers {
|
||||
// 统计域名
|
||||
for _, name := range server.ServerNames {
|
||||
uniqueDomains[name] = true
|
||||
}
|
||||
|
||||
// 统计 location
|
||||
totalLocations += len(server.Locations)
|
||||
|
||||
// 检测 SSL
|
||||
if server.SSL != nil {
|
||||
hasSSL = true
|
||||
analysis.Features["ssl"] = true
|
||||
}
|
||||
|
||||
// 检测 location 特性
|
||||
for _, loc := range server.Locations {
|
||||
if loc.ProxyPass != "" {
|
||||
analysis.Features["proxy"] = true
|
||||
}
|
||||
if len(loc.Rewrite) > 0 {
|
||||
hasRewrite = true
|
||||
analysis.Features["rewrite"] = true
|
||||
}
|
||||
if loc.Return != nil {
|
||||
analysis.Features["return"] = true
|
||||
if loc.Return.Code >= 300 && loc.Return.Code < 400 {
|
||||
analysis.Features["redirect"] = true
|
||||
}
|
||||
}
|
||||
if loc.Modifier != "" {
|
||||
hasComplexRouting = true
|
||||
analysis.Features["complex_routing"] = true
|
||||
}
|
||||
// 检测其他指令
|
||||
if _, ok := loc.Directives["proxy_set_header"]; ok {
|
||||
analysis.Features["header_manipulation"] = true
|
||||
}
|
||||
if _, ok := loc.Directives["add_header"]; ok {
|
||||
analysis.Features["response_headers"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
analysis.LocationCount = totalLocations
|
||||
analysis.DomainCount = len(uniqueDomains)
|
||||
|
||||
// 判断复杂度
|
||||
if analysis.ServerCount > 3 || totalLocations > 10 || (hasRewrite && hasSSL && hasComplexRouting) {
|
||||
analysis.Complexity = "high"
|
||||
} else if analysis.ServerCount > 1 || totalLocations > 5 || hasRewrite || hasSSL || hasUpstream {
|
||||
analysis.Complexity = "medium"
|
||||
}
|
||||
|
||||
// 生成建议
|
||||
if analysis.Features["proxy"] {
|
||||
analysis.Suggestions = append(analysis.Suggestions, "proxy_pass 将转换为 Ingress/HTTPRoute 的 backend 配置")
|
||||
}
|
||||
if analysis.Features["rewrite"] {
|
||||
analysis.Suggestions = append(analysis.Suggestions, "rewrite 规则需要使用 Higress 注解实现,如 higress.io/rewrite-target")
|
||||
}
|
||||
if analysis.Features["ssl"] {
|
||||
analysis.Suggestions = append(analysis.Suggestions, "SSL 证书需要创建 Kubernetes Secret,并在 Ingress 中引用")
|
||||
}
|
||||
if analysis.Features["redirect"] {
|
||||
analysis.Suggestions = append(analysis.Suggestions, "redirect 可以使用 Higress 的重定向注解或插件实现")
|
||||
}
|
||||
if hasUpstream {
|
||||
analysis.Suggestions = append(analysis.Suggestions, "upstream 负载均衡将由 Kubernetes Service 和 Endpoints 实现")
|
||||
}
|
||||
if analysis.Features["header_manipulation"] {
|
||||
analysis.Suggestions = append(analysis.Suggestions, "请求头操作可以使用 Higress 注解或 custom-response 插件实现")
|
||||
}
|
||||
|
||||
return analysis
|
||||
}
|
||||
|
||||
// NginxAnalysis 表示 Nginx 配置分析结果
|
||||
type NginxAnalysis struct {
|
||||
ServerCount int `json:"server_count"`
|
||||
LocationCount int `json:"location_count"`
|
||||
DomainCount int `json:"domain_count"`
|
||||
Features map[string]bool `json:"features"`
|
||||
Complexity string `json:"complexity"` // simple, medium, high
|
||||
Suggestions []string `json:"suggestions"`
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
// Tool Chain implementations for LLM-guided Lua to WASM conversion
|
||||
package tools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AnalysisResultForAI 结构化的分析结果,用于 AI 协作
|
||||
type AnalysisResultForAI struct {
|
||||
Features map[string]bool `json:"features"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
APICalls []string `json:"api_calls"`
|
||||
Warnings []string `json:"warnings"`
|
||||
Complexity string `json:"complexity"`
|
||||
Compatibility string `json:"compatibility"`
|
||||
// OriginalCode 字段已移除,避免返回大量数据
|
||||
}
|
||||
|
||||
// ConversionHints 代码转换提示(简化版)
|
||||
type ConversionHints struct {
|
||||
CodeTemplate string `json:"code_template"`
|
||||
Warnings []string `json:"warnings"`
|
||||
}
|
||||
|
||||
// ValidationReport 验证报告
|
||||
type ValidationReport struct {
|
||||
Issues []ValidationIssue `json:"issues"` // 所有发现的问题
|
||||
MissingImports []string `json:"missing_imports"` // 缺失的 import
|
||||
FoundCallbacks []string `json:"found_callbacks"` // 找到的回调函数
|
||||
HasConfig bool `json:"has_config"` // 是否有配置结构
|
||||
Summary string `json:"summary"` // 总体评估摘要
|
||||
}
|
||||
|
||||
// ValidationIssue 验证问题
|
||||
type ValidationIssue struct {
|
||||
Category string `json:"category"` // required, recommended, optional, best_practice
|
||||
Type string `json:"type"` // syntax, api_usage, config, error_handling, logging, etc.
|
||||
Message string `json:"message"` // 问题描述
|
||||
Suggestion string `json:"suggestion"` // 改进建议
|
||||
Impact string `json:"impact"` // 影响说明(为什么重要)
|
||||
}
|
||||
|
||||
// DeploymentPackage 部署配置包
|
||||
type DeploymentPackage struct {
|
||||
WasmPluginYAML string `json:"wasm_plugin_yaml"`
|
||||
Makefile string `json:"makefile"`
|
||||
Dockerfile string `json:"dockerfile"`
|
||||
ConfigMap string `json:"config_map"`
|
||||
README string `json:"readme"`
|
||||
TestScript string `json:"test_script"`
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
}
|
||||
|
||||
// AnalyzeLuaPluginForAI 分析 Lua 插件并生成 AI 友好的输出
|
||||
func AnalyzeLuaPluginForAI(luaCode string) AnalysisResultForAI {
|
||||
analyzer := AnalyzeLuaScript(luaCode)
|
||||
|
||||
// 收集所有 API 调用
|
||||
apiCalls := []string{}
|
||||
for feature := range analyzer.Features {
|
||||
apiCalls = append(apiCalls, feature)
|
||||
}
|
||||
|
||||
// 确定兼容性级别
|
||||
compatibility := "full"
|
||||
if len(analyzer.Warnings) > 0 {
|
||||
compatibility = "partial"
|
||||
}
|
||||
if len(analyzer.Warnings) > 2 {
|
||||
compatibility = "manual"
|
||||
}
|
||||
|
||||
return AnalysisResultForAI{
|
||||
Features: analyzer.Features,
|
||||
Variables: analyzer.Variables,
|
||||
APICalls: apiCalls,
|
||||
Warnings: analyzer.Warnings,
|
||||
Complexity: analyzer.Complexity,
|
||||
Compatibility: compatibility,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateConversionHints 生成代码转换提示(简化版)
|
||||
func GenerateConversionHints(analysis AnalysisResultForAI, pluginName string) ConversionHints {
|
||||
return ConversionHints{
|
||||
CodeTemplate: generateCodeTemplate(analysis, pluginName),
|
||||
Warnings: analysis.Warnings,
|
||||
}
|
||||
}
|
||||
|
||||
// generateCodeTemplate 生成代码模板提示
|
||||
func generateCodeTemplate(analysis AnalysisResultForAI, pluginName string) string {
|
||||
callbacks := generateCallbackSummary(analysis)
|
||||
return fmt.Sprintf(`生成 Go WASM 插件 %s,实现回调: %s
|
||||
参考文档: https://higress.cn/docs/latest/user/wasm-go/`,
|
||||
pluginName, callbacks)
|
||||
}
|
||||
|
||||
// generateCallbackRegistrations 生成回调注册代码
|
||||
func generateCallbackRegistrations(analysis AnalysisResultForAI) string {
|
||||
callbacks := []string{}
|
||||
|
||||
if analysis.Features["ngx.var"] || analysis.Features["request_headers"] || analysis.Features["header_manipulation"] {
|
||||
callbacks = append(callbacks, "wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders)")
|
||||
}
|
||||
|
||||
if analysis.Features["request_body"] {
|
||||
callbacks = append(callbacks, "wrapper.ProcessRequestBodyBy(onHttpRequestBody)")
|
||||
}
|
||||
|
||||
if analysis.Features["response_headers"] || analysis.Features["response_control"] {
|
||||
callbacks = append(callbacks, "wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders)")
|
||||
}
|
||||
|
||||
if len(callbacks) == 0 {
|
||||
callbacks = append(callbacks, "wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders)")
|
||||
}
|
||||
|
||||
return "\n\t\t" + strings.Join(callbacks, ",\n\t\t")
|
||||
}
|
||||
|
||||
// generateCallbackSummary 生成回调函数摘要
|
||||
func generateCallbackSummary(analysis AnalysisResultForAI) string {
|
||||
callbacks := []string{}
|
||||
|
||||
if analysis.Features["ngx.var"] || analysis.Features["request_headers"] || analysis.Features["header_manipulation"] {
|
||||
callbacks = append(callbacks, "onHttpRequestHeaders")
|
||||
}
|
||||
if analysis.Features["request_body"] {
|
||||
callbacks = append(callbacks, "onHttpRequestBody")
|
||||
}
|
||||
if analysis.Features["response_headers"] || analysis.Features["response_control"] {
|
||||
callbacks = append(callbacks, "onHttpResponseHeaders")
|
||||
}
|
||||
|
||||
if len(callbacks) == 0 {
|
||||
return "onHttpRequestHeaders"
|
||||
}
|
||||
return strings.Join(callbacks, ", ")
|
||||
}
|
||||
|
||||
// ValidateWasmCode 验证生成的 Go WASM 代码
|
||||
func ValidateWasmCode(goCode, pluginName string) ValidationReport {
|
||||
report := ValidationReport{
|
||||
Issues: []ValidationIssue{},
|
||||
MissingImports: []string{},
|
||||
FoundCallbacks: []string{},
|
||||
HasConfig: false,
|
||||
}
|
||||
|
||||
// 移除注释以避免误判
|
||||
codeWithoutComments := removeComments(goCode)
|
||||
|
||||
// 检查必要的包声明
|
||||
packagePattern := regexp.MustCompile(`(?m)^package\s+main\s*$`)
|
||||
if !packagePattern.MatchString(goCode) {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "required",
|
||||
Type: "syntax",
|
||||
Message: "缺少 'package main' 声明",
|
||||
Suggestion: "在文件开头添加: package main",
|
||||
Impact: "WASM 插件必须使用 package main,否则无法编译",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查 main 函数
|
||||
mainFuncPattern := regexp.MustCompile(`func\s+main\s*\(\s*\)`)
|
||||
if !mainFuncPattern.MatchString(codeWithoutComments) {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "required",
|
||||
Type: "syntax",
|
||||
Message: "缺少 main() 函数",
|
||||
Suggestion: "添加空的 main 函数: func main() {}",
|
||||
Impact: "WASM 插件必须有 main 函数,即使是空的",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查 init 函数
|
||||
initFuncPattern := regexp.MustCompile(`func\s+init\s*\(\s*\)`)
|
||||
if !initFuncPattern.MatchString(codeWithoutComments) {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "required",
|
||||
Type: "api_usage",
|
||||
Message: "缺少 init() 函数",
|
||||
Suggestion: "添加 init() 函数用于注册插件",
|
||||
Impact: "插件需要在 init() 中调用 wrapper.SetCtx 进行注册",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查 wrapper.SetCtx 调用
|
||||
setCtxPattern := regexp.MustCompile(`wrapper\.SetCtx\s*\(`)
|
||||
if !setCtxPattern.MatchString(codeWithoutComments) {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "required",
|
||||
Type: "api_usage",
|
||||
Message: "缺少 wrapper.SetCtx 调用",
|
||||
Suggestion: "在 init() 函数中调用 wrapper.SetCtx 注册插件上下文",
|
||||
Impact: "没有注册插件上下文将导致插件无法工作",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查必要的 import
|
||||
requiredImports := map[string]string{
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types": "定义了 Action 等核心类型",
|
||||
"github.com/higress-group/wasm-go/pkg/wrapper": "提供了 Higress 插件开发的高级封装",
|
||||
}
|
||||
|
||||
for importPath, reason := range requiredImports {
|
||||
if !containsImport(goCode, importPath) {
|
||||
report.MissingImports = append(report.MissingImports, importPath)
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "required",
|
||||
Type: "imports",
|
||||
Message: fmt.Sprintf("缺少必需的导入: %s", importPath),
|
||||
Suggestion: fmt.Sprintf(`添加导入: import "%s"`, importPath),
|
||||
Impact: reason,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 检查可选但推荐的 import
|
||||
if !containsImport(goCode, "github.com/higress-group/proxy-wasm-go-sdk/proxywasm") {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "optional",
|
||||
Type: "imports",
|
||||
Message: "未导入 proxywasm 包",
|
||||
Suggestion: "如需使用日志、HTTP 调用等底层 API,可导入 proxywasm 包",
|
||||
Impact: "proxywasm 提供了日志记录、外部 HTTP 调用等功能",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查配置结构体
|
||||
configPattern := regexp.MustCompile(`type\s+\w+Config\s+struct\s*\{`)
|
||||
report.HasConfig = configPattern.MatchString(goCode)
|
||||
|
||||
if !report.HasConfig {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "optional",
|
||||
Type: "config",
|
||||
Message: "未定义配置结构体",
|
||||
Suggestion: "如果插件需要配置参数,建议定义配置结构体(如 type MyPluginConfig struct { ... })",
|
||||
Impact: "配置结构体用于接收和解析插件的配置参数,支持动态配置",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查 parseConfig 函数
|
||||
parseConfigPattern := regexp.MustCompile(`func\s+parseConfig\s*\(`)
|
||||
hasParseConfig := parseConfigPattern.MatchString(codeWithoutComments)
|
||||
|
||||
if report.HasConfig && !hasParseConfig {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "recommended",
|
||||
Type: "config",
|
||||
Message: "定义了配置结构体但缺少 parseConfig 函数",
|
||||
Suggestion: "实现 parseConfig 函数来解析配置: func parseConfig(json gjson.Result, config *MyPluginConfig, log wrapper.Log) error",
|
||||
Impact: "parseConfig 函数负责将 JSON 配置解析到结构体,是配置系统的核心",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查回调函数
|
||||
callbacks := map[string]*regexp.Regexp{
|
||||
"onHttpRequestHeaders": regexp.MustCompile(`func\s+onHttpRequestHeaders\s*\(`),
|
||||
"onHttpRequestBody": regexp.MustCompile(`func\s+onHttpRequestBody\s*\(`),
|
||||
"onHttpResponseHeaders": regexp.MustCompile(`func\s+onHttpResponseHeaders\s*\(`),
|
||||
"onHttpResponseBody": regexp.MustCompile(`func\s+onHttpResponseBody\s*\(`),
|
||||
}
|
||||
|
||||
for name, pattern := range callbacks {
|
||||
if pattern.MatchString(codeWithoutComments) {
|
||||
report.FoundCallbacks = append(report.FoundCallbacks, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(report.FoundCallbacks) == 0 {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "required",
|
||||
Type: "api_usage",
|
||||
Message: "未找到任何 HTTP 回调函数实现",
|
||||
Suggestion: "至少实现一个回调函数,如: func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyPluginConfig, log wrapper.Log) types.Action",
|
||||
Impact: "回调函数是插件逻辑的核心,没有回调函数插件将不会执行任何操作",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查错误处理
|
||||
errHandlingCount := strings.Count(codeWithoutComments, "if err != nil")
|
||||
funcCount := strings.Count(codeWithoutComments, "func ")
|
||||
if funcCount > 3 && errHandlingCount == 0 {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "best_practice",
|
||||
Type: "error_handling",
|
||||
Message: "代码中缺少错误处理",
|
||||
Suggestion: "对可能返回错误的操作添加错误检查: if err != nil { ... }",
|
||||
Impact: "良好的错误处理可以提高插件的健壮性和可调试性",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查日志记录
|
||||
hasLogging := strings.Contains(codeWithoutComments, "proxywasm.Log") ||
|
||||
strings.Contains(codeWithoutComments, "log.Error") ||
|
||||
strings.Contains(codeWithoutComments, "log.Warn") ||
|
||||
strings.Contains(codeWithoutComments, "log.Info") ||
|
||||
strings.Contains(codeWithoutComments, "log.Debug")
|
||||
|
||||
if !hasLogging {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "best_practice",
|
||||
Type: "logging",
|
||||
Message: "代码中没有日志记录",
|
||||
Suggestion: "添加适当的日志记录,如: proxywasm.LogInfo(), log.Errorf() 等",
|
||||
Impact: "日志记录有助于调试、监控和问题排查",
|
||||
})
|
||||
}
|
||||
|
||||
// 检查回调函数的返回值
|
||||
checkCallbackReturnErrors(&report, codeWithoutComments, report.FoundCallbacks)
|
||||
|
||||
// 生成总体评估摘要
|
||||
report.Summary = generateValidationSummary(report)
|
||||
|
||||
return report
|
||||
}
|
||||
|
||||
// removeComments 移除 Go 代码中的注释
|
||||
func removeComments(code string) string {
|
||||
// 移除单行注释
|
||||
singleLineComment := regexp.MustCompile(`//.*`)
|
||||
code = singleLineComment.ReplaceAllString(code, "")
|
||||
|
||||
// 移除多行注释
|
||||
multiLineComment := regexp.MustCompile(`(?s)/\*.*?\*/`)
|
||||
code = multiLineComment.ReplaceAllString(code, "")
|
||||
|
||||
return code
|
||||
}
|
||||
|
||||
// containsImport 检查是否包含特定的 import
|
||||
func containsImport(code, importPath string) bool {
|
||||
// 匹配 import "path" 或 import ("path")
|
||||
pattern := regexp.MustCompile(`import\s+(?:\([\s\S]*?)?["` + "`" + `]` +
|
||||
regexp.QuoteMeta(importPath) + `["` + "`" + `]`)
|
||||
return pattern.MatchString(code)
|
||||
}
|
||||
|
||||
// checkCallbackReturnErrors 检查回调函数的返回值错误
|
||||
func checkCallbackReturnErrors(report *ValidationReport, code string, foundCallbacks []string) {
|
||||
// 检查回调函数内是否有 return nil(应该返回 types.Action)
|
||||
for _, callback := range foundCallbacks {
|
||||
// 提取回调函数体(简化的检查)
|
||||
funcPattern := regexp.MustCompile(
|
||||
`func\s+` + callback + `\s*\([^)]*\)\s+types\.Action\s*\{[^}]*return\s+nil[^}]*\}`)
|
||||
if funcPattern.MatchString(code) {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "required",
|
||||
Type: "api_usage",
|
||||
Message: fmt.Sprintf("回调函数 %s 不应返回 nil", callback),
|
||||
Suggestion: "回调函数应返回 types.Action,如: return types.ActionContinue",
|
||||
Impact: "返回 nil 会导致编译错误或运行时异常",
|
||||
})
|
||||
break // 只报告一次
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否正确返回 types.Action
|
||||
if len(foundCallbacks) > 0 {
|
||||
hasActionReturn := strings.Contains(code, "types.ActionContinue") ||
|
||||
strings.Contains(code, "types.ActionPause") ||
|
||||
strings.Contains(code, "types.ActionSuspend")
|
||||
|
||||
if !hasActionReturn {
|
||||
report.Issues = append(report.Issues, ValidationIssue{
|
||||
Category: "recommended",
|
||||
Type: "api_usage",
|
||||
Message: "未找到明确的 Action 返回值",
|
||||
Suggestion: "回调函数应返回明确的 types.Action 值(ActionContinue、ActionPause 等)",
|
||||
Impact: "明确的返回值有助于代码可读性和正确性",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generateValidationSummary 生成验证摘要
|
||||
func generateValidationSummary(report ValidationReport) string {
|
||||
requiredIssues := 0
|
||||
recommendedIssues := 0
|
||||
optionalIssues := 0
|
||||
bestPracticeIssues := 0
|
||||
|
||||
for _, issue := range report.Issues {
|
||||
switch issue.Category {
|
||||
case "required":
|
||||
requiredIssues++
|
||||
case "recommended":
|
||||
recommendedIssues++
|
||||
case "optional":
|
||||
optionalIssues++
|
||||
case "best_practice":
|
||||
bestPracticeIssues++
|
||||
}
|
||||
}
|
||||
|
||||
if requiredIssues > 0 {
|
||||
return fmt.Sprintf("代码存在 %d 个必须修复的问题,%d 个建议修复的问题,%d 个可选优化项,%d 个最佳实践建议。请优先解决必须修复的问题。",
|
||||
requiredIssues, recommendedIssues, optionalIssues, bestPracticeIssues)
|
||||
}
|
||||
|
||||
if recommendedIssues > 0 {
|
||||
return fmt.Sprintf("代码基本结构正确,但有 %d 个建议修复的问题,%d 个可选优化项,%d 个最佳实践建议。",
|
||||
recommendedIssues, optionalIssues, bestPracticeIssues)
|
||||
}
|
||||
|
||||
if optionalIssues > 0 || bestPracticeIssues > 0 {
|
||||
return fmt.Sprintf("代码结构良好,有 %d 个可选优化项和 %d 个最佳实践建议可以考虑。",
|
||||
optionalIssues, bestPracticeIssues)
|
||||
}
|
||||
|
||||
callbacksInfo := ""
|
||||
if len(report.FoundCallbacks) > 0 {
|
||||
callbacksInfo = fmt.Sprintf(",实现了 %d 个回调函数", len(report.FoundCallbacks))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("代码验证通过,未发现明显问题%s。", callbacksInfo)
|
||||
}
|
||||
|
||||
// GenerateDeploymentPackage 生成部署配置提示(由 LLM 根据代码生成具体内容)
|
||||
func GenerateDeploymentPackage(pluginName, goCode, configSchema, namespace string) DeploymentPackage {
|
||||
// 所有配置文件都由 LLM 根据实际代码生成,不使用固定模板
|
||||
return DeploymentPackage{
|
||||
WasmPluginYAML: "", // LLM 生成
|
||||
Makefile: "", // LLM 生成
|
||||
Dockerfile: "", // LLM 生成
|
||||
ConfigMap: "", // LLM 生成
|
||||
README: "", // LLM 生成
|
||||
TestScript: "", // LLM 生成
|
||||
Dependencies: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// FormatToolResultWithAIContext 格式化工具结果
|
||||
func FormatToolResultWithAIContext(userMessage, aiInstructions string, structuredData interface{}) ToolResult {
|
||||
jsonData, _ := json.MarshalIndent(structuredData, "", " ")
|
||||
output := fmt.Sprintf("%s\n\n%s\n\n%s", userMessage, aiInstructions, string(jsonData))
|
||||
return ToolResult{
|
||||
Content: []Content{{Type: "text", Text: output}},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user