feat: 适配后端接口文档v4,更新工具和Agent创建逻辑

- 更新 createTool 接口:使用 template + envConfig 格式替代 type + config
- 更新 createCustomAgent 接口:添加 tools 参数支持,从工具获取配置
- 更新 updateTool 接口:支持 is_active 和 envConfig 参数
- 工具列表展示 template、description、is_active 字段
- 部署Agent时从已创建的工具列表中选择
- 环境变量配置过滤 OPENAI_API_KEY(系统自动注入)
This commit is contained in:
zhanggangyong
2026-01-13 12:51:04 +00:00
parent 5c43b49746
commit 6672904036
2 changed files with 168 additions and 122 deletions
+122 -89
View File
@@ -285,6 +285,7 @@ export default function DataToolsPage() {
}
// 创建数据模块(实际调用创建工具接口)
// 根据接口文档 v4:使用 template + envConfig 格式
const handleCreateTemplate = async () => {
if (!templateName) {
alert(t("请输入模块名称", "Please enter module name"))
@@ -294,9 +295,9 @@ export default function DataToolsPage() {
try {
setCreatingTemplate(true)
let config: any = {}
let toolType = "api" // 默认工具类型
let description = ""
let template = ""
let toolEnvConfig: Record<string, string> = {}
// 数据存储模块(来自API的dataTemplates)
if (templateType === "data_storage" as any) {
@@ -305,62 +306,43 @@ export default function DataToolsPage() {
return
}
const template = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!template) {
const templateInfo = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!templateInfo) {
alert(t("无效的模块", "Invalid module"))
return
}
// 校验必填环境变量
const required = template.env_info?.required || {}
// 校验必填环境变量(排除 OPENAI_API_KEY,系统会自动注入)
const required = templateInfo.env_info?.required || {}
for (const key of Object.keys(required)) {
// OPENAI_API_KEY 无需用户填写,系统自动注入
if (key === "OPENAI_API_KEY") continue
if (!envConfig[key]) {
alert(t(`请填写 ${key}`, `Please fill in ${key}`))
return
}
}
toolType = "data_storage"
config.template = selectedApiDataTemplate
config.port = template.port
config.envConfig = envConfig
description = template.description || t("数据存储工具", "Data Storage Tool")
template = selectedApiDataTemplate
toolEnvConfig = { ...envConfig }
description = templateInfo.description || t("数据存储工具", "Data Storage Tool")
} else if (templateType === "json_api") {
// JSON API 模块
toolType = "api"
if (useJsonUpload && templateConfig.jsonContent) {
config.jsonContent = templateConfig.jsonContent
description = t("JSON API工具(JSON内容)", "JSON API Tool (JSON Content)")
} else if (templateConfig.url) {
config.endpoint = templateConfig.url
config.method = "GET"
if (templateConfig.apiKey) config.apiKey = templateConfig.apiKey
if (templateConfig.queryParams) config.queryParams = templateConfig.queryParams
description = t("JSON API工具", "JSON API Tool")
} else {
alert(t("请提供API URL或JSON内容", "Please provide API URL or JSON content"))
// JSON API 模块 - 暂不支持新格式,保留原逻辑提示
alert(t("JSON API模块即将支持,请使用数据存储模块", "JSON API module coming soon, please use data storage module"))
return
}
} else if (templateType === "cloud_storage") {
// 云存储模块
toolType = "cloud_storage"
if (!templateConfig.service || !templateConfig.connectionString) {
alert(t("请填写存储服务和连接字符串", "Please fill in storage service and connection string"))
// 云存储模块 - 暂不支持新格式,保留原逻辑提示
alert(t("云存储模块即将支持,请使用数据存储模块", "Cloud storage module coming soon, please use data storage module"))
return
}
config.service = templateConfig.service
config.connectionString = templateConfig.connectionString
config.endpoint = `storage://${templateConfig.service}`
config.method = "GET"
description = t("云存储工具", "Cloud Storage Tool")
}
// 使用创建工具接口 POST /api/user/tools/create
// 根据接口文档 v4 格式:name, description, template, envConfig
const result = await TaijiAPIClient.createTool({
name: templateName,
description: description || t("数据模块工具", "Data Module Tool"),
type: toolType,
config: config,
template: template,
envConfig: toolEnvConfig,
})
if (result?.success) {
@@ -395,10 +377,27 @@ export default function DataToolsPage() {
}
// 部署Agent(创建自定义Agent)
// 根据接口文档 v4:使用 tools 参数,系统自动从工具获取 template 和 envConfig
const handleDeployAgent = async () => {
// 校验必填字段
if (!selectedApiDataTemplate || !toolName) {
alert(t("请选择数据存储模块并填写Agent名称", "Please select a data module and enter agent name"))
// 校验必填字段:Agent名称必填,工具或数据存储模块二选一
if (!toolName) {
alert(t("请填写Agent名称", "Please enter agent name"))
return
}
// 获取选中的工具(从已创建的工具列表中选择)
const selectedToolIds = selectedDataTemplate ? [selectedDataTemplate] : []
// 如果没有选择已有工具,但选择了数据存储模块,需要提示先创建工具
if (selectedToolIds.length === 0 && selectedApiDataTemplate) {
// 如果选了模板但没选工具,说明要直接用模板创建(旧模式)
// 新接口要求必须先创建工具,再基于工具创建Agent
alert(t("请先在「工具模块」标签页创建工具,然后再部署Agent", "Please create a tool first in the 'Tool Modules' tab, then deploy the Agent"))
return
}
if (selectedToolIds.length === 0) {
alert(t("请选择一个已创建的工具", "Please select a created tool"))
return
}
@@ -414,20 +413,20 @@ export default function DataToolsPage() {
// 框架类型(默认MCP)
const frameworkName = selectedFramework || "MCP"
// 构建请求参数
// 构建请求参数 - 根据接口文档 v4 格式
const requestData: any = {
name: toolName,
template: selectedApiDataTemplate, // 必填:数据存储模块(如 mysql_agent)
name: toolName, // 必填
tools: selectedToolIds, // 必填:工具ID列表
frameworkTemplate: frameworkName, // 可选:框架类型(A2A/langchain/MCP)
description: toolName, // 可选:Agent描述
cpuRequest: cpuRequest, // 必填:CPU请求量
cpuRequest: cpuRequest, // 可选:CPU请求量,默认 "100m"
cpuLimit: cpuLimit, // 可选:CPU限制量
memoryRequest: memoryRequest, // 必填:内存请求量
memoryRequest: memoryRequest, // 可选:内存请求量,默认 "128Mi"
memoryLimit: memoryLimit, // 可选:内存限制量
model: podConfig.model, // 可选:模型名称
}
// 添加环境变量配置(如数据库连接信息)
// 添加额外环境变量配置(与工具配置合并,请求中的优先)
if (Object.keys(envConfig).length > 0) {
requestData.envConfig = envConfig
}
@@ -451,6 +450,7 @@ export default function DataToolsPage() {
// 重置表单
setSelectedFramework("")
setSelectedServiceGateway("")
setSelectedDataTemplate("")
setSelectedApiDataTemplate("")
setEnvConfig({})
setAgentRole("")
@@ -595,9 +595,9 @@ export default function DataToolsPage() {
<TableHeader>
<TableRow>
<TableHead>{t("工具名称", "Tool Name")}</TableHead>
<TableHead>{t("类别", "Category")}</TableHead>
<TableHead>{t("方法", "Method")}</TableHead>
<TableHead>{t("端点", "Endpoint")}</TableHead>
<TableHead>{t("描述", "Description")}</TableHead>
<TableHead>{t("模板类型", "Template")}</TableHead>
<TableHead>{t("分类", "Category")}</TableHead>
<TableHead>{t("状态", "Status")}</TableHead>
<TableHead>{t("创建时间", "Created")}</TableHead>
<TableHead className="text-right">{t("操作", "Actions")}</TableHead>
@@ -620,18 +620,22 @@ export default function DataToolsPage() {
tools.map((tool) => (
<TableRow key={tool.id || tool.name}>
<TableCell className="font-medium">{tool.name}</TableCell>
<TableCell>
<Badge variant="secondary">{tool.type || tool.category || "-"}</Badge>
<TableCell className="text-sm text-muted-foreground max-w-[200px] truncate">
{tool.description || "-"}
</TableCell>
<TableCell>
<Badge variant="outline">{tool.config?.method || tool.method || "-"}</Badge>
<Badge variant="outline">{tool.template || "-"}</Badge>
</TableCell>
<TableCell className="text-xs text-muted-foreground">{tool.config?.endpoint || tool.config?.url || tool.url || "-"}</TableCell>
<TableCell>
<Badge className="bg-green-500/10 text-green-500">{tool.status || "active"}</Badge>
<Badge variant="secondary">{tool.category || tool.type || "database"}</Badge>
</TableCell>
<TableCell>
<Badge className={tool.is_active !== false ? "bg-green-500/10 text-green-500" : "bg-gray-500/10 text-gray-500"}>
{tool.is_active !== false ? t("激活", "Active") : t("停用", "Inactive")}
</Badge>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{tool.created_at ? new Date(tool.created_at).toLocaleString() : tool.created || "-"}
{tool.created_at ? new Date(tool.created_at).toLocaleString() : "-"}
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end gap-2">
@@ -935,31 +939,34 @@ export default function DataToolsPage() {
</div>
<div className="space-y-2">
<Label>{t("数据存储模块", "Data Storage Module")} <span className="text-destructive">*</span></Label>
<Label>{t("选择工具", "Select Tool")} <span className="text-destructive">*</span></Label>
<Select
value={selectedApiDataTemplate}
value={selectedDataTemplate}
onValueChange={(value) => {
setSelectedApiDataTemplate(value)
// 重置环境变量配置
setEnvConfig({})
setSelectedDataTemplate(value)
// 找到对应工具,可以显示其模板信息
const tool = dataTemplates.find(t => t.id === value)
if (tool?.template) {
setSelectedApiDataTemplate(tool.template)
}
}}
>
<SelectTrigger>
<SelectValue placeholder={t("选择数据存储模块", "Select data storage module")} />
<SelectValue placeholder={t("选择已创建的工具", "Select a created tool")} />
</SelectTrigger>
<SelectContent>
{apiDataTemplates.length === 0 ? (
{dataTemplates.length === 0 ? (
<SelectItem value="" disabled>
{t("暂无可用模块", "No modules available")}
{t("暂无可用工具,请先创建工具", "No tools available, please create a tool first")}
</SelectItem>
) : (
apiDataTemplates.map((template) => (
<SelectItem key={template.template} value={template.template}>
dataTemplates.map((tool) => (
<SelectItem key={tool.id} value={tool.id}>
<div className="flex flex-col">
<span>{template.template}</span>
{template.description && (
<span className="text-xs text-muted-foreground">{template.description}</span>
)}
<span>{tool.name}</span>
<span className="text-xs text-muted-foreground">
{tool.template || tool.category || t("工具", "Tool")}
</span>
</div>
</SelectItem>
))
@@ -967,23 +974,33 @@ export default function DataToolsPage() {
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{t("选择Agent使用的数据存储类型", "Select data storage type for the agent")}
{t("从已创建的工具中选择,系统将自动获取工具的模板和配置", "Select from created tools, system will automatically get template and config from tool")}
</p>
</div>
</div>
{/* 动态环境变量配置(根据选择的数据存储模块) */}
{selectedApiDataTemplate && (() => {
{/* 注意:如果选择了已有工具,通常不需要再填写环境变量,因为系统会从工具中获取 */}
{selectedApiDataTemplate && !selectedDataTemplate && (() => {
const template = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!template?.env_info) return null
const required = template.env_info.required || {}
const optional = template.env_info.optional || {}
// 过滤掉 OPENAI_API_KEY,系统会自动注入
const required = Object.entries(template.env_info.required || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
const optional = Object.entries(template.env_info.optional || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
if (required.length === 0 && optional.length === 0) return null
return (
<div className="border rounded-lg p-4 space-y-3 bg-muted/30">
<h4 className="font-medium text-sm">{t("数据库连接配置", "Database Connection Config")}</h4>
{Object.entries(required).map(([key, desc]) => (
<h4 className="font-medium text-sm">{t("额外环境变量配置(可选)", "Additional Environment Config (Optional)")}</h4>
<p className="text-xs text-muted-foreground">
{t("如需覆盖工具的默认配置,可在此填写", "Fill in here if you need to override tool's default config")}
</p>
{required.map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm">{key} <span className="text-destructive">*</span></Label>
<Label className="text-sm">{key}</Label>
<Input
placeholder={String(desc)}
value={envConfig[key] || ""}
@@ -992,12 +1009,12 @@ export default function DataToolsPage() {
/>
</div>
))}
{Object.keys(optional).length > 0 && (
{optional.length > 0 && (
<>
<div className="border-t pt-2 mt-2">
<p className="text-xs text-muted-foreground mb-2">{t("可选配置", "Optional Config")}</p>
</div>
{Object.entries(optional).map(([key, desc]) => (
{optional.map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm text-muted-foreground">{key}</Label>
<Input
@@ -1093,7 +1110,18 @@ export default function DataToolsPage() {
{t("Agent名称", "Agent Name")}: {toolName || t("未填写", "Not filled")}
</div>
<div className="col-span-2">
{t("数据存储模块", "Data Module")}: {selectedApiDataTemplate || t("未选择", "Not selected")}
{t("使用工具", "Tool")}: {
selectedDataTemplate
? (dataTemplates.find(t => t.id === selectedDataTemplate)?.name || selectedDataTemplate)
: t("未选择", "Not selected")
}
</div>
<div className="col-span-2">
{t("工具模板", "Tool Template")}: {
selectedDataTemplate
? (dataTemplates.find(t => t.id === selectedDataTemplate)?.template || "-")
: "-"
}
</div>
<div className="col-span-2">
{t("框架", "Framework")}: {selectedFramework || "MCP"}
@@ -1121,7 +1149,7 @@ export default function DataToolsPage() {
<Button variant="outline" onClick={() => setShowNewToolDialog(false)} disabled={deploying}>
{t("取消", "Cancel")}
</Button>
<Button onClick={handleDeployAgent} disabled={deploying || !selectedApiDataTemplate || !toolName}>
<Button onClick={handleDeployAgent} disabled={deploying || !selectedDataTemplate || !toolName}>
{deploying ? t("部署中...", "Deploying...") : t("部署Agent", "Deploy Agent")}
</Button>
</DialogFooter>
@@ -1230,18 +1258,23 @@ export default function DataToolsPage() {
/>
</div>
{/* 动态环境变量配置 - 完全根据后端返回的 env_info 生成,不添加任何额外字段 */}
{/* 动态环境变量配置 - 根据后端返回的 env_info 生成,过滤掉 OPENAI_API_KEY(系统自动注入) */}
{selectedApiDataTemplate && (() => {
const template = apiDataTemplates.find(t => t.template === selectedApiDataTemplate)
if (!template?.env_info) return null
// 只使用后端返回的 required 和 optional 字段,不添加任何额外字段
const required = template.env_info.required || {}
const optional = template.env_info.optional || {}
// 过滤掉 OPENAI_API_KEY,系统会自动注入用户的 LiteLLM 密钥
const required = Object.entries(template.env_info.required || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
const optional = Object.entries(template.env_info.optional || {})
.filter(([key]) => key !== "OPENAI_API_KEY")
return (
<div className="border rounded-lg p-4 space-y-3 bg-muted/30">
<h4 className="font-medium text-sm">{t("连接配置", "Connection Config")}</h4>
{/* 只渲染后端返回的必填字段 */}
{Object.entries(required).map(([key, desc]) => (
<p className="text-xs text-muted-foreground">
{t("注意:API密钥由系统自动注入,无需填写", "Note: API key is automatically injected by system, no need to fill in")}
</p>
{/* 只渲染后端返回的必填字段(排除 OPENAI_API_KEY) */}
{required.map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm">{key} <span className="text-destructive">*</span></Label>
<Input
@@ -1252,13 +1285,13 @@ export default function DataToolsPage() {
/>
</div>
))}
{/* 只渲染后端返回的可选字段 */}
{Object.keys(optional).length > 0 && (
{/* 只渲染后端返回的可选字段(排除 OPENAI_API_KEY) */}
{optional.length > 0 && (
<>
<div className="border-t pt-2 mt-2">
<p className="text-xs text-muted-foreground mb-2">{t("可选配置", "Optional Config")}</p>
</div>
{Object.entries(optional).map(([key, desc]) => (
{optional.map(([key, desc]) => (
<div key={key} className="space-y-1">
<Label className="text-sm text-muted-foreground">{key}</Label>
<Input
+43 -30
View File
@@ -1,9 +1,9 @@
// API client for Taiji AI Platform
// Base URLs for different services
export const API_BASE_URLS = {
dataIngestion: process.env.NEXT_PUBLIC_DATA_INGESTION_URL || "http://localhost:8002",
mcpServer: process.env.NEXT_PUBLIC_MCP_SERVER_URL || "http://localhost:8002",
gateway: process.env.NEXT_PUBLIC_API_GATEWAY_URL || "http://localhost:8002",
dataIngestion: process.env.NEXT_PUBLIC_DATA_INGESTION_URL || "https://apimtaiji.azure-api.net/api/mcp",
mcpServer: process.env.NEXT_PUBLIC_MCP_SERVER_URL || "https://apimtaiji.azure-api.net/api/mcp",
gateway: process.env.NEXT_PUBLIC_API_GATEWAY_URL || "https://apimtaiji.azure-api.net/api/mcp",
}
import { getAuthToken, clearAllTokens } from "@/lib/auth"
@@ -682,41 +682,38 @@ export class TaijiAPIClient {
/**
* 创建自定义Agent
* POST /api/user/custom-agents
* 创建租户自定义的Agent,将使用渠道为该租户分配的CPU和内存资源配额
*
* 注意:后端实际路径为 /api/user/custom-agents(非 /api/user/agents/custom/create)
* 根据接口文档 v4 格式:
* 核心逻辑:创建 Agent 时选择已创建的工具,系统自动从工具获取 template 和 envConfig,
* 调用 Agent Manager 创建对应类型的 Agent。
*
* @param data.name - Agent名称(小写字母、数字、连字符)
* @param data.template - 数据存储模板(如 "mysql_agent"、"postgresql_agent")
* @param data.name - Agent名称(小写字母、数字、连字符,1-63字符)【必填】
* @param data.tools - 工具ID列表(第一个工具决定Agent类型和配置)【必填】
* @param data.template - 模板名称(可选,未指定时从工具获取)
* @param data.frameworkTemplate - 框架类型("MCP" | "A2A" | "langchain"),默认 "MCP"
* @param data.description - Agent描述
* @param data.cpuRequest - CPU请求量(如 "500m")
* @param data.cpuLimit - CPU限制量
* @param data.memoryRequest - 内存请求量(如 "1Gi")
* @param data.memoryLimit - 内存限制量
* @param data.tools - 工具ID列表(UUID)
* @param data.model - 模型名称(如 "gpt-4")
* @param data.endpoint - 自定义终结点
* @param data.apiKey - API密钥
* @param data.envConfig - 环境变量配置(数据库连接等)
* @param data.cpuRequest - CPU请求量(如 "500m"),默认 "100m"
* @param data.cpuLimit - CPU限制量,默认等于 cpuRequest
* @param data.memoryRequest - 内存请求量(如 "1Gi"),默认 "128Mi"
* @param data.memoryLimit - 内存限制量,默认等于 memoryRequest
* @param data.model - 模型名称(用于注入 LiteLLM 密钥)
* @param data.envConfig - 额外环境变量(与工具配置合并,请求中的优先)
* @param data.agentRole - A2A框架专用:Agent角色(如 "data_analyzer")
* @param data.agentCapabilities - A2A框架专用:Agent能力列表
*/
static async createCustomAgent(data: {
name: string
template: string // 必填:数据存储模板
name: string // 必填
tools: string[] // 必填:工具ID列表
template?: string // 可选:模板名称(未指定时从工具获取)
frameworkTemplate?: string // 可选:框架类型
description?: string
cpuRequest: string
cpuRequest?: string
cpuLimit?: string
memoryRequest: string
memoryRequest?: string
memoryLimit?: string
tools?: string[]
model?: string // 新增:模型名称
endpoint?: string
apiKey?: string
model?: string
envConfig?: Record<string, string>
// A2A 框架专用(新增)
// A2A 框架专用
agentRole?: string
agentCapabilities?: string[]
}) {
@@ -827,14 +824,22 @@ export class TaijiAPIClient {
}
/**
* 创建工具
* 创建工具(基于模板)
* POST /api/user/tools/create
*
* 根据接口文档 v4 格式:
* - name: 工具名称
* - description: 工具描述(可选)
* - template: 模板名称(来自 dataTemplates[].template,如 mysql_agent)
* - envConfig: 环境变量配置(根据模板 env_info 填写)
*
* 注意:OPENAI_API_KEY 无需填写,系统会自动注入用户的 LiteLLM 密钥
*/
static async createTool(data: {
name: string
description: string
type: string
config: any
description?: string
template: string // 模板名称(如 mysql_agent, postgresql_agent)
envConfig: Record<string, string> // 环境变量配置
}) {
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/user/tools/create`, {
method: "POST",
@@ -847,10 +852,18 @@ export class TaijiAPIClient {
/**
* 修改工具
* PUT /api/user/tools/{tool_id}
*
* 根据接口文档 v4 格式:
* - description: 工具描述(可选)
* - is_active: 是否激活(可选)
* - envConfig: 更新环境变量配置(可选)
*
* 注意:template 字段创建后不可修改
*/
static async updateTool(toolId: string, data: {
description?: string
config?: any
is_active?: boolean
envConfig?: Record<string, string>
}) {
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/user/tools/${toolId}`, {
method: "PUT",