feat(agent): pre-bind skills and tools when applying an agent template

This commit is contained in:
matevip 2026-05-12 07:11:19 +08:00
parent f0d6755469
commit 201e510b17
6 changed files with 134 additions and 0 deletions

View File

@ -30,6 +30,25 @@ public class TemplateDTO {
private String systemPrompt;
private List<WorkspaceFileTemplate> workspaceFiles;
/**
* Skill slugs (matching {@code mate_skill.name}) to pre-bind to the newly
* hired agent. Resolved against the target workspace at apply time; any
* slug whose row is missing in that workspace is logged and skipped so a
* partially-installed environment can still hire the agent. Templates ship
* with classpath-stable slugs, not numeric IDs, because skill ids vary per
* install.
*/
private List<String> defaultSkillSlugs;
/**
* Tool names to pre-bind directly (bypassing the skill layer). Filtered
* against {@code AvailableToolService.listAvailable()} at apply time
* names the picker can't resolve are dropped with a warning rather than
* aborting the hire. Use for capabilities that aren't owned by any skill,
* not for system-level tools that are already universally available.
*/
private List<String> defaultToolNames;
@Data
public static class WorkspaceFileTemplate {
private String filename;

View File

@ -1,5 +1,6 @@
package vip.mate.agent.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -8,9 +9,14 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.mate.agent.AgentService;
import vip.mate.agent.binding.service.AgentBindingService;
import vip.mate.agent.model.AgentEntity;
import vip.mate.agent.model.TemplateDTO;
import vip.mate.exception.MateClawException;
import vip.mate.skill.model.SkillEntity;
import vip.mate.skill.repository.SkillMapper;
import vip.mate.tool.model.AvailableToolDTO;
import vip.mate.tool.service.AvailableToolService;
import vip.mate.workspace.document.WorkspaceFileService;
import java.io.IOException;
@ -18,6 +24,7 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
@ -36,6 +43,9 @@ public class TemplateService {
private final AgentService agentService;
private final WorkspaceFileService workspaceFileService;
private final ObjectMapper objectMapper;
private final AgentBindingService agentBindingService;
private final SkillMapper skillMapper;
private final AvailableToolService availableToolService;
/**
* 列出所有可用模板
@ -135,9 +145,94 @@ public class TemplateService {
}
}
// 4. Pre-bind skills the template declares so a hired agent is
// usable out of the box ("数据分析师" already knows SQL, "代码审查员"
// already has the test-driven-development playbook). Slugs are
// resolved against the target workspace; missing skills are skipped
// with a warning so a partially-installed environment can still
// complete the hire.
applyDefaultSkillBindings(template, created, workspaceId);
// 5. Pre-bind any standalone tools the template wants. Filtered
// against the picker so a deprecated / unavailable tool name in the
// template doesn't abort the hire same forgiving stance as
// skills above.
applyDefaultToolBindings(template, created);
return created;
}
/**
* Resolve {@link TemplateDTO#getDefaultSkillSlugs()} to skill ids inside
* {@code workspaceId} and pre-bind them on the freshly-created agent.
* Slugs whose row is missing in the workspace are logged and dropped a
* template MUST be safe to apply even when some bundled skills haven't
* landed yet (offline upgrade, partial seed, custom workspace).
*/
private void applyDefaultSkillBindings(TemplateDTO template, AgentEntity created, Long workspaceId) {
List<String> slugs = template.getDefaultSkillSlugs();
if (slugs == null || slugs.isEmpty()) return;
List<Long> resolvedIds = new ArrayList<>();
for (String slug : slugs) {
if (slug == null || slug.isBlank()) continue;
SkillEntity skill = skillMapper.selectOne(new LambdaQueryWrapper<SkillEntity>()
.eq(SkillEntity::getName, slug.trim())
.eq(SkillEntity::getWorkspaceId, workspaceId));
if (skill == null) {
log.warn("[Template] template {} requested skill slug '{}' not found in workspace {}; skipping",
template.getId(), slug, workspaceId);
continue;
}
resolvedIds.add(skill.getId());
}
if (resolvedIds.isEmpty()) return;
agentBindingService.setSkillBindings(created.getId(), resolvedIds);
log.info("[Template] template {} pre-bound {} skill(s) on agent {}",
template.getId(), resolvedIds.size(), created.getId());
}
/**
* Pre-filter the template's tool names through the picker so
* {@link AgentBindingService#setToolBindings} sees only resolvable names
* its own validation would otherwise abort the call on the first
* unknown name and leave the agent with no tool bindings at all.
*/
private void applyDefaultToolBindings(TemplateDTO template, AgentEntity created) {
List<String> names = template.getDefaultToolNames();
if (names == null || names.isEmpty()) return;
Set<String> bindable;
try {
bindable = availableToolService.listAvailable().stream()
.filter(AvailableToolDTO::isAvailable)
.map(AvailableToolDTO::getName)
.collect(Collectors.toSet());
} catch (Exception e) {
log.warn("[Template] picker unavailable during template {} apply; skipping tool pre-bind: {}",
template.getId(), e.getMessage());
return;
}
List<String> filtered = new ArrayList<>();
for (String name : names) {
if (name == null || name.isBlank()) continue;
String trimmed = name.trim();
if (bindable.contains(trimmed)) {
filtered.add(trimmed);
} else {
log.warn("[Template] template {} requested tool '{}' not currently bindable; skipping",
template.getId(), trimmed);
}
}
if (filtered.isEmpty()) return;
agentBindingService.setToolBindings(created.getId(), filtered);
log.info("[Template] template {} pre-bound {} tool(s) on agent {}",
template.getId(), filtered.size(), created.getId());
}
/**
* True when the raw Accept-Language header best-matches a Chinese locale.
* Implementation is intentionally simple we only need to disambiguate

View File

@ -8,6 +8,12 @@
"agentType": "react",
"tags": "code,review,developer",
"maxIterations": 10,
"defaultSkillSlugs": [
"systematic-debugging",
"test-driven-development",
"requesting-code-review",
"subagent-driven-development"
],
"systemPrompt": "## Role\n代码审查员\n\n## Goal\n找到不该在 PR 里的东西\n\n## Backstory\n你是见过太多周五下午合并事故的资深审查员。你信奉一条被合进 main 的代码,要么经得起半年后的回头看,要么不该进。你读代码先读改动的边界——它影响哪些调用方、哪些边缘情况、哪些隐藏假设。你直接但不刻薄,每一条意见都附上修法。\n\n## Additional Instructions\n审查清单逻辑错误与边缘情况安全漏洞性能瓶颈命名与可读性错误处理完备性测试覆盖盲区。先读完整段再下结论不要只看 diff 的±号。\n",
"workspaceFiles": [
{

View File

@ -8,6 +8,10 @@
"agentType": "react",
"tags": "data,analysis,sql",
"maxIterations": 12,
"defaultSkillSlugs": [
"sql_query",
"xlsx"
],
"systemPrompt": "## Role\n数据分析师\n\n## Goal\n把数据变成可执行的洞察\n\n## Backstory\n你在数据里待了十年。最大的体会是80% 的烂分析栽在第一步——问题没问对。所以你拿到任何需求都先停一下,确认\"我们到底想知道什么\",再决定要拉哪张表。你写 SQL 简洁、加注释,不堆 CTE 炫技。出结论时永远带数据范围、口径定义和置信度。\n\n## Additional Instructions\n工作流程1) 复述问题确认理解2) 列出关键指标与维度3) 写查询并注明口径4) 给一句话结论 + 一张关键图 + 三条建议。不要把表格堆给用户,要把判断给他。\n",
"workspaceFiles": [
{

View File

@ -8,6 +8,11 @@
"agentType": "react",
"tags": "product,prd,requirements",
"maxIterations": 12,
"defaultSkillSlugs": [
"ideation",
"make_plan",
"writing-plans"
],
"systemPrompt": "## Role\n产品助理\n\n## Goal\n把模糊需求理成可执行的 PRD\n\n## Backstory\n你做产品做久了知道一句话需求背后通常藏着三个不一样的问题。所以你拿到任何描述先把它翻译成\"用户是谁 + 他在什么场景下 + 他想达成什么 + 现在的痛点是什么\"。你写 PRD 不堆功能列表,会先讲清楚\"不做什么\"和\"成功长什么样\"。\n\n## Additional Instructions\n输出结构1) 用户与场景2) 目标与反目标不做什么3) 核心流程4) 验收标准。一段话能讲清的不用列表,能列清的不用图。讲清楚\"为什么\"比讲清楚\"做什么\"更重要。\n",
"workspaceFiles": [
{

View File

@ -8,6 +8,11 @@
"agentType": "plan_execute",
"tags": "research,analysis,planning",
"maxIterations": 20,
"defaultSkillSlugs": [
"arxiv",
"news",
"x_intel"
],
"systemPrompt": "## Role\n研究分析员\n\n## Goal\n把信息整理成可下结论的判断\n\n## Backstory\n你像图书馆员一样固执——没有可信来源你不下结论。你做研究的步骤是固定的先把大问题拆成可独立查证的小问题再分别取证最后交叉对照。看到两个来源说反话你不会偷偷选一个会原样列出并标注分歧。\n\n## Additional Instructions\n研究流程1) 拆解问题2) 用网络搜索拿最新事实3) 在 Wiki 知识库找已有分析4) 多源交叉验证5) 对每条结论标注信心等级。准确高于速度。证据不足时直接说\"我不知道\"。\n",
"workspaceFiles": [
{