агенты: добавить поиск инструментов по каталогу
This commit is contained in:
+192
-20
@@ -14,6 +14,7 @@ function mapAgent(agent) {
|
||||
raw_status: agent.status,
|
||||
operation_count: agent.operation_count || 0,
|
||||
operation_ids: agent.operation_ids || [],
|
||||
tool_selection_policy: agent.tool_selection_policy || { mode: 'direct', groups: [], search: { max_results: 8 } },
|
||||
key_count: agent.key_count || 0,
|
||||
calls_today: agent.calls_today || 0,
|
||||
created_at: agent.created_at,
|
||||
@@ -66,10 +67,18 @@ document.addEventListener('alpine:init', function() {
|
||||
description: '',
|
||||
status: 'published',
|
||||
selectedOps: [],
|
||||
accessMode: 'direct',
|
||||
groups: [],
|
||||
searchMaxResults: 8,
|
||||
},
|
||||
|
||||
opSearch: '',
|
||||
slugManuallyEdited: false,
|
||||
searchPreviewQuery: '',
|
||||
searchPreviewGroup: '',
|
||||
searchPreviewItems: [],
|
||||
searchPreviewLoading: false,
|
||||
searchPreviewRan: false,
|
||||
|
||||
async init() {
|
||||
var self = this;
|
||||
@@ -205,7 +214,7 @@ document.addEventListener('alpine:init', function() {
|
||||
get agentToolFindings() {
|
||||
var findings = [];
|
||||
var selected = this.selectedOperations;
|
||||
if (selected.length > 8) {
|
||||
if (selected.length > 8 && this.form.accessMode === 'direct') {
|
||||
findings.push(this.tKey('agents.drawer.finding.too_many_tools'));
|
||||
}
|
||||
|
||||
@@ -242,13 +251,18 @@ document.addEventListener('alpine:init', function() {
|
||||
description: '',
|
||||
status: 'published',
|
||||
selectedOps: [],
|
||||
accessMode: 'direct',
|
||||
groups: [],
|
||||
searchMaxResults: 8,
|
||||
};
|
||||
this.opSearch = '';
|
||||
this.slugManuallyEdited = false;
|
||||
this.resetSearchPreview();
|
||||
this.drawerOpen = true;
|
||||
},
|
||||
|
||||
openEdit(agent) {
|
||||
var policy = agent.tool_selection_policy || {};
|
||||
this.drawerMode = 'edit';
|
||||
this.editingId = agent.id;
|
||||
this.form = {
|
||||
@@ -257,9 +271,22 @@ document.addEventListener('alpine:init', function() {
|
||||
description: agent.description,
|
||||
status: agent.raw_status || agent.status || 'draft',
|
||||
selectedOps: [].concat(agent.operation_ids || []),
|
||||
accessMode: policy.mode === 'search' ? 'search' : 'direct',
|
||||
groups: (policy.groups || []).map(function(group) {
|
||||
return {
|
||||
id: group.id || '',
|
||||
name: group.name || '',
|
||||
description: group.description || '',
|
||||
tool_names: [].concat(group.tool_names || []),
|
||||
};
|
||||
}),
|
||||
searchMaxResults: policy.search && policy.search.max_results
|
||||
? policy.search.max_results
|
||||
: 8,
|
||||
};
|
||||
this.opSearch = '';
|
||||
this.slugManuallyEdited = true;
|
||||
this.resetSearchPreview();
|
||||
this.drawerOpen = true;
|
||||
},
|
||||
|
||||
@@ -289,13 +316,170 @@ document.addEventListener('alpine:init', function() {
|
||||
this.form.selectedOps.push(operationId);
|
||||
} else {
|
||||
this.form.selectedOps.splice(index, 1);
|
||||
var operation = this.operations.find(function(item) { return item.id === operationId; });
|
||||
if (operation) {
|
||||
this.form.groups.forEach(function(group) {
|
||||
group.tool_names = group.tool_names.filter(function(name) {
|
||||
return name !== operation.name;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
isOpSelected(operationId) {
|
||||
return this.form.selectedOps.includes(operationId);
|
||||
},
|
||||
|
||||
clearSelectedOperations() {
|
||||
this.form.selectedOps = [];
|
||||
this.form.groups.forEach(function(group) { group.tool_names = []; });
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
setAccessMode(mode) {
|
||||
this.form.accessMode = mode === 'search' ? 'search' : 'direct';
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
addToolGroup() {
|
||||
this.form.groups.push({ id: '', name: '', description: '', tool_names: [] });
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
removeToolGroup(index) {
|
||||
this.form.groups.splice(index, 1);
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
onToolGroupName(index, value) {
|
||||
var group = this.form.groups[index];
|
||||
if (!group) return;
|
||||
var previousSlug = this.slugifyGroupName(group.name);
|
||||
group.name = value;
|
||||
if (!group.id || group.id === previousSlug) {
|
||||
group.id = this.slugifyGroupName(value);
|
||||
}
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
onToolGroupId(index, value) {
|
||||
var group = this.form.groups[index];
|
||||
if (!group) return;
|
||||
group.id = this.slugifyGroupName(value);
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
slugifyGroupName(value) {
|
||||
return String(value || '')
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
},
|
||||
|
||||
toggleToolGroup(groupIndex, toolName) {
|
||||
var group = this.form.groups[groupIndex];
|
||||
if (!group) return;
|
||||
var index = group.tool_names.indexOf(toolName);
|
||||
if (index === -1) group.tool_names.push(toolName);
|
||||
else group.tool_names.splice(index, 1);
|
||||
this.resetSearchPreview();
|
||||
},
|
||||
|
||||
toolInGroup(groupIndex, toolName) {
|
||||
var group = this.form.groups[groupIndex];
|
||||
return Boolean(group && group.tool_names.includes(toolName));
|
||||
},
|
||||
|
||||
toolSelectionPolicy() {
|
||||
return {
|
||||
mode: this.form.accessMode,
|
||||
groups: this.form.accessMode === 'search'
|
||||
? this.form.groups.map(function(group) {
|
||||
return {
|
||||
id: group.id.trim(),
|
||||
name: group.name.trim(),
|
||||
description: group.description.trim(),
|
||||
tool_names: [].concat(group.tool_names || []),
|
||||
};
|
||||
})
|
||||
: [],
|
||||
search: {
|
||||
max_results: Math.max(1, Math.min(20, Number(this.form.searchMaxResults) || 8)),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
get catalogConfigValid() {
|
||||
if (this.form.accessMode !== 'search') return true;
|
||||
var ids = [];
|
||||
for (var index = 0; index < this.form.groups.length; index += 1) {
|
||||
var group = this.form.groups[index];
|
||||
if (!group.id.trim() || !group.name.trim() || !group.description.trim()) return false;
|
||||
if (ids.includes(group.id.trim())) return false;
|
||||
ids.push(group.id.trim());
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
agentBindings() {
|
||||
var self = this;
|
||||
return this.form.selectedOps.map(function(operationId) {
|
||||
var operation = self.operations.find(function(item) { return item.id === operationId; });
|
||||
return {
|
||||
operation_id: operationId,
|
||||
operation_version: operation && operation.latest_published_version
|
||||
? operation.latest_published_version
|
||||
: operation && operation.current_draft_version
|
||||
? operation.current_draft_version
|
||||
: 1,
|
||||
tool_name: operation ? operation.name : operationId,
|
||||
tool_title: operation ? (operation.display_name || operation.name) : operationId,
|
||||
tool_description_override: null,
|
||||
enabled: true,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
resetSearchPreview() {
|
||||
this.searchPreviewItems = [];
|
||||
this.searchPreviewRan = false;
|
||||
},
|
||||
|
||||
async previewToolSearch() {
|
||||
if (
|
||||
this.searchPreviewLoading
|
||||
|| !this.workspaceId
|
||||
|| !this.searchPreviewQuery.trim()
|
||||
|| this.form.accessMode !== 'search'
|
||||
) return;
|
||||
this.searchPreviewLoading = true;
|
||||
this.searchPreviewRan = false;
|
||||
try {
|
||||
var response = await window.CrankApi.previewAgentToolSearch(this.workspaceId, {
|
||||
query: this.searchPreviewQuery.trim(),
|
||||
group_ids: this.searchPreviewGroup ? [this.searchPreviewGroup] : [],
|
||||
bindings: this.agentBindings(),
|
||||
tool_selection_policy: this.toolSelectionPolicy(),
|
||||
});
|
||||
this.searchPreviewItems = response.items || [];
|
||||
this.searchPreviewRan = true;
|
||||
} catch (error) {
|
||||
this.searchPreviewItems = [];
|
||||
if (window.CrankUi) {
|
||||
window.CrankUi.error(
|
||||
error.message || this.tKey('agents.drawer.search.preview_error'),
|
||||
this.tKey('agents.drawer.search.preview_error_title')
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this.searchPreviewLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
operationsLookSimilar(left, right) {
|
||||
var leftTokens = this.operationTokens(left);
|
||||
var rightTokens = this.operationTokens(right);
|
||||
@@ -345,7 +529,7 @@ document.addEventListener('alpine:init', function() {
|
||||
display_name: this.form.display_name,
|
||||
description: this.form.description,
|
||||
instructions: {},
|
||||
tool_selection_policy: {},
|
||||
tool_selection_policy: this.toolSelectionPolicy(),
|
||||
});
|
||||
agentId = created.agent_id;
|
||||
currentVersion = created.version || 1;
|
||||
@@ -359,27 +543,15 @@ document.addEventListener('alpine:init', function() {
|
||||
currentVersion = agent.current_draft_version || 1;
|
||||
}
|
||||
|
||||
await window.CrankApi.saveAgentBindings(
|
||||
var savedVersion = await window.CrankApi.saveAgentBindings(
|
||||
this.workspaceId,
|
||||
agentId,
|
||||
this.form.selectedOps.map(function(operationId) {
|
||||
var operation = self.operations.find(function(item) {
|
||||
return item.id === operationId;
|
||||
});
|
||||
return {
|
||||
operation_id: operationId,
|
||||
operation_version: operation && operation.latest_published_version
|
||||
? operation.latest_published_version
|
||||
: operation && operation.current_draft_version
|
||||
? operation.current_draft_version
|
||||
: 1,
|
||||
tool_name: operation ? operation.name : operationId,
|
||||
tool_title: operation ? (operation.display_name || operation.name) : operationId,
|
||||
tool_description_override: null,
|
||||
enabled: true,
|
||||
};
|
||||
}),
|
||||
{
|
||||
bindings: this.agentBindings(),
|
||||
tool_selection_policy: this.toolSelectionPolicy(),
|
||||
},
|
||||
);
|
||||
currentVersion = savedVersion.version || currentVersion;
|
||||
|
||||
if (this.form.status === 'published') {
|
||||
await window.CrankApi.publishAgent(this.workspaceId, agentId, {
|
||||
|
||||
@@ -262,6 +262,9 @@
|
||||
saveAgentBindings: function(workspaceId, agentId, payload) {
|
||||
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/bindings', payload);
|
||||
},
|
||||
previewAgentToolSearch: function(workspaceId, payload) {
|
||||
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/tool-search/preview', payload);
|
||||
},
|
||||
publishAgent: function(workspaceId, agentId, payload) {
|
||||
return post('/workspaces/' + encodeURIComponent(workspaceId) + '/agents/' + encodeURIComponent(agentId) + '/publish', payload);
|
||||
},
|
||||
|
||||
+60
-4
@@ -819,13 +819,41 @@ var TRANSLATIONS = {
|
||||
'agents.drawer.operations_sub': 'Select the MCP tools available to this agent.',
|
||||
'agents.drawer.operations_sub_community': 'Select the MCP tools available to this agent.',
|
||||
'agents.drawer.finding.title': 'Recommendation',
|
||||
'agents.drawer.finding.too_many_tools': 'This agent has many tools. Keep only the tools needed for one concrete task.',
|
||||
'agents.drawer.finding.too_many_tools': 'This agent has many tools. Use on-demand selection or keep only the tools needed for one concrete task.',
|
||||
'agents.drawer.finding.similar_tools': 'Tools “{left}” and “{right}” look similar. Rename them more precisely or keep one of them.',
|
||||
'agents.drawer.filter_ops': 'Filter operations…',
|
||||
'agents.drawer.ops_no_match': 'No operations match "{query}"',
|
||||
'agents.drawer.ops_selected': '{count} operations selected',
|
||||
'agents.drawer.clear_all': 'Clear all',
|
||||
'agents.drawer.recommendation': "You've selected {count} tools. LLMs usually work best when an agent has fewer than 15 tools. Consider splitting this into separate agents by use case.",
|
||||
'agents.drawer.recommendation': "You've selected {count} tools. Switch to on-demand selection so the model receives only relevant schemas.",
|
||||
'agents.drawer.access.title': 'Tool access',
|
||||
'agents.drawer.access.subtitle': "Choose how the model receives this agent's tool catalog.",
|
||||
'agents.drawer.access.direct': 'Show tools immediately',
|
||||
'agents.drawer.access.direct_hint': 'Best for a small curated catalog. MCP clients receive every tool in tools/list.',
|
||||
'agents.drawer.access.search': 'Select tools on demand',
|
||||
'agents.drawer.access.search_hint': 'The model sees search_tools and call_tool, then discovers only relevant schemas.',
|
||||
'agents.drawer.groups.title': 'Catalog sections',
|
||||
'agents.drawer.groups.subtitle': 'Sections help the model narrow a search without blocking catalog-wide discovery.',
|
||||
'agents.drawer.groups.add': 'Add section',
|
||||
'agents.drawer.groups.empty': 'No sections yet. Search will use the entire selected catalog.',
|
||||
'agents.drawer.groups.untitled': 'Untitled section',
|
||||
'agents.drawer.groups.remove': 'Remove section',
|
||||
'agents.drawer.groups.name': 'Name',
|
||||
'agents.drawer.groups.name_placeholder': 'Finance',
|
||||
'agents.drawer.groups.id': 'Identifier',
|
||||
'agents.drawer.groups.description': 'Description for the model',
|
||||
'agents.drawer.groups.description_placeholder': 'Invoices, payments and refunds',
|
||||
'agents.drawer.groups.assign': 'Assign tools to sections',
|
||||
'agents.drawer.search.limit': 'Maximum results per search',
|
||||
'agents.drawer.search.preview_title': 'Test tool selection',
|
||||
'agents.drawer.search.preview_subtitle': 'Enter a task and verify which tools the model will receive.',
|
||||
'agents.drawer.search.query_placeholder': 'Create an invoice for a customer',
|
||||
'agents.drawer.search.all_groups': 'All sections',
|
||||
'agents.drawer.search.test': 'Test',
|
||||
'agents.drawer.search.testing': 'Testing…',
|
||||
'agents.drawer.search.no_results': 'No preview results yet.',
|
||||
'agents.drawer.search.preview_error': 'Failed to test tool selection',
|
||||
'agents.drawer.search.preview_error_title': 'Tool selection test failed',
|
||||
'agents.drawer.cancel': 'Cancel',
|
||||
'agents.drawer.create': 'Create agent',
|
||||
'agents.drawer.save': 'Save changes',
|
||||
@@ -1683,13 +1711,41 @@ var TRANSLATIONS = {
|
||||
'agents.drawer.operations_sub': 'Выберите MCP инструменты, которые будут доступны для этого агента.',
|
||||
'agents.drawer.operations_sub_community': 'Выберите MCP инструменты, которые будут доступны для этого агента.',
|
||||
'agents.drawer.finding.title': 'Рекомендация',
|
||||
'agents.drawer.finding.too_many_tools': 'У агента выбрано много инструментов. Оставьте только те, которые нужны для одной конкретной задачи.',
|
||||
'agents.drawer.finding.too_many_tools': 'У агента выбрано много инструментов. Включите подбор по запросу или оставьте только инструменты для одной конкретной задачи.',
|
||||
'agents.drawer.finding.similar_tools': 'Инструменты «{left}» и «{right}» похожи. Переименуйте их точнее или оставьте один вариант.',
|
||||
'agents.drawer.filter_ops': 'Фильтр операций…',
|
||||
'agents.drawer.ops_no_match': 'Нет операций по запросу "{query}"',
|
||||
'agents.drawer.ops_selected': 'Выбрано операций: {count}',
|
||||
'agents.drawer.clear_all': 'Очистить все',
|
||||
'agents.drawer.recommendation': 'Сейчас выбрано {count} инструментов. LLM лучше работает, когда у агента меньше 15 инструментов. Подумайте о разбиении по сценариям.',
|
||||
'agents.drawer.recommendation': 'Сейчас выбрано {count} инструментов. Включите подбор по запросу, чтобы модель получала только подходящие схемы.',
|
||||
'agents.drawer.access.title': 'Доступ к инструментам',
|
||||
'agents.drawer.access.subtitle': 'Выберите, как модель будет получать каталог инструментов этого агента.',
|
||||
'agents.drawer.access.direct': 'Показывать сразу',
|
||||
'agents.drawer.access.direct_hint': 'Для небольшого отобранного каталога. MCP-клиент получает все инструменты через tools/list.',
|
||||
'agents.drawer.access.search': 'Подбирать по запросу',
|
||||
'agents.drawer.access.search_hint': 'Модель видит search_tools и call_tool, а затем получает только подходящие схемы.',
|
||||
'agents.drawer.groups.title': 'Разделы каталога',
|
||||
'agents.drawer.groups.subtitle': 'Разделы сужают область поиска, но не мешают искать по всему каталогу.',
|
||||
'agents.drawer.groups.add': 'Добавить раздел',
|
||||
'agents.drawer.groups.empty': 'Разделов пока нет. Поиск будет выполняться по всему выбранному каталогу.',
|
||||
'agents.drawer.groups.untitled': 'Раздел без названия',
|
||||
'agents.drawer.groups.remove': 'Удалить раздел',
|
||||
'agents.drawer.groups.name': 'Название',
|
||||
'agents.drawer.groups.name_placeholder': 'Расчёты',
|
||||
'agents.drawer.groups.id': 'Идентификатор',
|
||||
'agents.drawer.groups.description': 'Описание для модели',
|
||||
'agents.drawer.groups.description_placeholder': 'Счета, платежи и возвраты',
|
||||
'agents.drawer.groups.assign': 'Распределение инструментов по разделам',
|
||||
'agents.drawer.search.limit': 'Максимум результатов за один поиск',
|
||||
'agents.drawer.search.preview_title': 'Проверка подбора',
|
||||
'agents.drawer.search.preview_subtitle': 'Введите задачу и проверьте, какие инструменты получит модель.',
|
||||
'agents.drawer.search.query_placeholder': 'Создать счёт для клиента',
|
||||
'agents.drawer.search.all_groups': 'Все разделы',
|
||||
'agents.drawer.search.test': 'Проверить',
|
||||
'agents.drawer.search.testing': 'Проверяем…',
|
||||
'agents.drawer.search.no_results': 'Результатов проверки пока нет.',
|
||||
'agents.drawer.search.preview_error': 'Не удалось проверить подбор инструментов',
|
||||
'agents.drawer.search.preview_error_title': 'Ошибка проверки подбора',
|
||||
'agents.drawer.cancel': 'Отмена',
|
||||
'agents.drawer.create': 'Создать агента',
|
||||
'agents.drawer.save': 'Сохранить изменения',
|
||||
|
||||
Reference in New Issue
Block a user