mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(agent): pre-bind skills and tools when applying an agent template
This commit is contained in:
parent
f0d6755469
commit
201e510b17
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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": [
|
||||
{
|
||||
|
||||
@ -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": [
|
||||
{
|
||||
|
||||
@ -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": [
|
||||
{
|
||||
|
||||
@ -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": [
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user