Skip to content

Commit 52baa70

Browse files
pythonHuangclaude
andcommitted
feat: 模型能力绑定-大模型供应商可设置支持的能力(技能多选/接口多选/流程多选/工具逗号列表,JSON存储),流程AI节点按所选供应商过滤技能列表(供应商配置了支持技能时只显示其支持的)
Co-Authored-By: Claude Code <noreply@anthropic.com>
1 parent aa13483 commit 52baa70

6 files changed

Lines changed: 99 additions & 1 deletion

File tree

‎Juggle.Api/Controllers/Api/AiController.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public async Task<ApiResult> SaveProvider([FromBody] AiProviderSaveRequest req)
101101
entity.Model = req.Model;
102102
entity.Models = req.Models;
103103
entity.Enabled = req.Enabled ? 1 : 0;
104+
entity.Capabilities = req.Capabilities;
104105
entity.Remark = req.Remark;
105106
entity.UpdatedAt = DateTime.Now.ToString("o");
106107
await _db.SaveChangesAsync();
@@ -446,6 +447,7 @@ public class AiProviderSaveRequest
446447
public string ApiKey { get; set; } = "";
447448
public string Model { get; set; } = "";
448449
public string Models { get; set; } = "";
450+
public string? Capabilities { get; set; }
449451
public bool Enabled { get; set; } = true;
450452
public string? Remark { get; set; }
451453
}

‎Juggle.Api/Program.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ await ctx.Response.WriteAsync(
262262
try { db.Database.ExecuteSqlRaw("CREATE TABLE IF NOT EXISTS t_skill(id INTEGER PRIMARY KEY AUTOINCREMENT, skill_name TEXT, group_name TEXT, description TEXT, content TEXT, enabled INTEGER DEFAULT 1, market_skill_id INTEGER, created_at TEXT, created_by INTEGER, updated_at TEXT, updated_by INTEGER, tenant_id INTEGER, deleted INTEGER DEFAULT 0);"); } catch { }
263263
// 市场条目表
264264
try { db.Database.ExecuteSqlRaw("CREATE TABLE IF NOT EXISTS t_market_item(id INTEGER PRIMARY KEY AUTOINCREMENT, item_type TEXT, item_name TEXT, description TEXT, group_name TEXT, content_json TEXT, download_count INTEGER DEFAULT 0, enabled INTEGER DEFAULT 1, created_at TEXT, created_by INTEGER, updated_at TEXT, updated_by INTEGER, tenant_id INTEGER, deleted INTEGER DEFAULT 0);"); } catch { }
265+
// AI 供应商能力字段
266+
try { db.Database.ExecuteSqlRaw("ALTER TABLE t_ai_provider ADD COLUMN capabilities TEXT DEFAULT NULL;"); } catch { }
265267
// 知识库 4 表
266268
try { db.Database.ExecuteSqlRaw("CREATE TABLE IF NOT EXISTS t_kb(id INTEGER PRIMARY KEY AUTOINCREMENT, kb_name TEXT, description TEXT, chunk_size INTEGER DEFAULT 500, chunk_overlap INTEGER DEFAULT 50, retrieve_type TEXT DEFAULT 'text', vector_model TEXT, enabled INTEGER DEFAULT 1, chunk_count INTEGER DEFAULT 0, created_at TEXT, created_by INTEGER, updated_at TEXT, updated_by INTEGER, tenant_id INTEGER, deleted INTEGER DEFAULT 0);"); } catch { }
267269
try { db.Database.ExecuteSqlRaw("CREATE TABLE IF NOT EXISTS t_kb_document(id INTEGER PRIMARY KEY AUTOINCREMENT, kb_id INTEGER, doc_name TEXT, doc_type TEXT, content TEXT, status INTEGER DEFAULT 0, created_at TEXT, created_by INTEGER, updated_at TEXT, updated_by INTEGER, tenant_id INTEGER, deleted INTEGER DEFAULT 0);"); } catch { }

‎Juggle.Domain/Entities/AiProviderEntity.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public class AiProviderEntity : BaseEntity
2424
/// <summary>是否启用:1=启用 0=禁用</summary>
2525
public int Enabled { get; set; } = 1;
2626

27+
/// <summary>支持的能力(JSON: {skills:[id], apis:[methodCode], flows:[flowKey], tools:[工具名]})</summary>
28+
public string? Capabilities { get; set; }
29+
2730
/// <summary>备注</summary>
2831
public string? Remark { get; set; }
2932
}

‎Juggle.Infrastructure/Persistence/JuggleDbContext.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
113113
e.Property(p => p.Model).HasColumnName("model");
114114
e.Property(p => p.Models).HasColumnName("models");
115115
e.Property(p => p.Enabled).HasColumnName("enabled");
116+
e.Property(p => p.Capabilities).HasColumnName("capabilities");
116117
e.Property(p => p.Remark).HasColumnName("remark");
117118
});
118119
modelBuilder.Entity<AiAssistantEntity>().ToTable("t_ai_assistant");

‎JuggleNet6.Frontend/src/views/flow/FlowDesign.vue‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@
324324
<label>技能 Skills</label>
325325
<el-select v-model="skillIdsModel" size="small" style="width:100%" multiple collapse-tags collapse-tags-tooltip
326326
clearable placeholder="勾选要附加的技能(执行时拼入系统提示词)">
327-
<el-option v-for="s in skillList" :key="s.id" :label="`${s.skillName}(${s.groupName})`" :value="s.id" />
327+
<el-option v-for="s in aiNodeSkillOptions" :key="s.id" :label="`${s.skillName}(${s.groupName})`" :value="s.id" />
328328
</el-select>
329329
<div style="font-size:11px;color:#909399;margin-top:4px">技能在 系统设置 → Skill 管理 中维护;执行时按「技能名 + 内容」拼入系统提示词。</div>
330330
</div>
@@ -1980,6 +1980,18 @@ const skillIdsModel = computed({
19801980
}
19811981
})
19821982
1983+
// 按所选供应商的"支持的能力"过滤技能列表(供应商配置了 skills 时只显示其支持的)
1984+
const aiNodeSkillOptions = computed(() => {
1985+
const provider = aiProviders.value.find((p: any) => p.id === (selectedNode.value?.aiConfig?.providerId || 0))
1986+
try {
1987+
const caps = JSON.parse(provider?.capabilities || '{}')
1988+
if (Array.isArray(caps.skills) && caps.skills.length > 0) {
1989+
return skillList.value.filter((s: any) => caps.skills.includes(s.id))
1990+
}
1991+
} catch { /* 未配置能力时显示全部 */ }
1992+
return skillList.value
1993+
})
1994+
19831995
async function loadSkillList() {
19841996
try {
19851997
const res: any = await request.get('/skill/list', { params: { enabled: 1 } })

‎JuggleNet6.Frontend/src/views/system/AiProviderList.vue‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,32 @@
5757
<el-option v-for="m in fetchedModels" :key="m" :label="m" :value="m" />
5858
</el-select>
5959
</el-form-item>
60+
<el-form-item label="支持的能力">
61+
<div style="width:100%">
62+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
63+
<span style="width:40px;font-size:12px;color:#666">Skills</span>
64+
<el-select v-model="capSkills" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip clearable placeholder="勾选该模型支持的技能">
65+
<el-option v-for="s in skillOptions" :key="s.id" :label="`${s.skillName}(${s.groupName})`" :value="s.id" />
66+
</el-select>
67+
</div>
68+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
69+
<span style="width:40px;font-size:12px;color:#666">接口</span>
70+
<el-select v-model="capApis" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip filterable clearable placeholder="勾选该模型可调用的接口">
71+
<el-option v-for="a in apiOptions" :key="a.methodCode" :label="`${a.methodName}(${a.methodCode})`" :value="a.methodCode" />
72+
</el-select>
73+
</div>
74+
<div style="display:flex;gap:8px;align-items:center;margin-bottom:6px">
75+
<span style="width:40px;font-size:12px;color:#666">流程</span>
76+
<el-select v-model="capFlows" size="small" style="flex:1" multiple collapse-tags collapse-tags-tooltip filterable clearable placeholder="勾选该模型可调用的流程">
77+
<el-option v-for="f in flowOptions" :key="f.flowKey" :label="`${f.flowName}(${f.flowKey})`" :value="f.flowKey" />
78+
</el-select>
79+
</div>
80+
<div style="display:flex;gap:8px;align-items:center">
81+
<span style="width:40px;font-size:12px;color:#666">工具</span>
82+
<el-input v-model="capTools" size="small" style="flex:1" placeholder="逗号分隔的工具名(如 web_search, calculator)" />
83+
</div>
84+
</div>
85+
</el-form-item>
6086
<el-form-item label="启用"><el-switch v-model="form.enabled" /></el-form-item>
6187
<el-form-item label="备注"><el-input v-model="form.remark" /></el-form-item>
6288
</el-form>
@@ -83,6 +109,48 @@ const selectedModels = ref<string[]>([])
83109
const testingConfig = ref(false)
84110
const testMsg = ref('')
85111
const testOk = ref(false)
112+
// 支持的能力
113+
const capSkills = ref<number[]>([])
114+
const capApis = ref<string[]>([])
115+
const capFlows = ref<string[]>([])
116+
const capTools = ref('')
117+
const skillOptions = ref<any[]>([])
118+
const apiOptions = ref<any[]>([])
119+
const flowOptions = ref<any[]>([])
120+
121+
async function loadCapabilityOptions() {
122+
try {
123+
const [skillRes, suiteRes, flowRes]: any[] = await Promise.all([
124+
request.get('/skill/list', { params: { enabled: 1 } }),
125+
request.get('/suite/list'),
126+
request.post('/flow/definition/page', { pageNum: 1, pageSize: 500 })
127+
])
128+
skillOptions.value = skillRes.data || []
129+
flowOptions.value = flowRes.data?.list || []
130+
const suites = suiteRes.data || []
131+
apiOptions.value = []
132+
for (const s of suites) {
133+
try {
134+
const apisRes: any = await request.post('/suite/api/list', { suiteCode: s.suiteCode })
135+
for (const a of (apisRes.data || [])) {
136+
apiOptions.value.push({ methodCode: a.methodCode, methodName: a.methodName })
137+
}
138+
} catch { /* 忽略单个套件失败 */ }
139+
}
140+
} catch { /* 忽略 */ }
141+
}
142+
143+
function parseCapabilities(json: string) {
144+
try {
145+
const c = JSON.parse(json || '{}')
146+
capSkills.value = c.skills || []
147+
capApis.value = c.apis || []
148+
capFlows.value = c.flows || []
149+
capTools.value = (c.tools || []).join(',')
150+
} catch {
151+
capSkills.value = []; capApis.value = []; capFlows.value = []; capTools.value = ''
152+
}
153+
}
86154
87155
// 预置供应商(选择后自动填默认接口地址)
88156
const presetProviders = [
@@ -108,15 +176,19 @@ function openAdd() {
108176
form.value = { id: 0, providerName: '', baseUrl: '', apiKey: '', model: '', models: '', enabled: true, remark: '' }
109177
fetchedModels.value = []
110178
selectedModels.value = []
179+
parseCapabilities('')
111180
testMsg.value = ''
112181
dialogVisible.value = true
182+
loadCapabilityOptions()
113183
}
114184
function openEdit(row: any) {
115185
form.value = { ...row, enabled: row.enabled === 1 }
116186
fetchedModels.value = String(row.models || '').split(',').map((s: string) => s.trim()).filter(Boolean)
117187
selectedModels.value = [...fetchedModels.value]
188+
parseCapabilities(row.capabilities || '')
118189
testMsg.value = ''
119190
dialogVisible.value = true
191+
loadCapabilityOptions()
120192
}
121193
122194
/** 选择预置供应商 → 自动填接口地址默认值 */
@@ -150,6 +222,12 @@ async function doFetchModels() {
150222
151223
async function doSave() {
152224
form.value.models = selectedModels.value.join(',')
225+
form.value.capabilities = JSON.stringify({
226+
skills: capSkills.value,
227+
apis: capApis.value,
228+
flows: capFlows.value,
229+
tools: capTools.value.split(',').map((s: string) => s.trim()).filter(Boolean)
230+
})
153231
await request.post('/ai/provider/save', form.value)
154232
ElMessage.success('保存成功')
155233
dialogVisible.value = false

0 commit comments

Comments
 (0)