Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions backend/apps/skill_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
logger = logging.getLogger(__name__)

router = APIRouter(prefix="/skills", tags=["skills"])
skill_creator_router = APIRouter(prefix="/skills", tags=["simple-skills"])
skill_creator_router = APIRouter(prefix="/skills", tags=["nl2skill"])


class SkillCreateRequest(BaseModel):
Expand Down Expand Up @@ -462,6 +462,7 @@ async def delete_skill(
class SkillCreateSimpleRequest(BaseModel):
"""Request model for interactive skill creation."""
user_request: str
existing_skill: Optional[Dict[str, Any]] = None


def _build_model_config_from_tenant(tenant_id: str) -> ModelConfig:
Expand Down Expand Up @@ -519,7 +520,10 @@ async def generate():
try:
_, tenant_id, language = get_current_user_info(authorization)

template = get_skill_creation_simple_prompt_template(language)
template = get_skill_creation_simple_prompt_template(
language,
existing_skill=request.existing_skill
)

model_config = _build_model_config_from_tenant(tenant_id)
observer = MessageObserver(lang=language)
Expand Down
41 changes: 38 additions & 3 deletions backend/prompts/skill_creation_simple_zh.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
system_prompt: |-
你是一个专业的技能创建助手,用于帮助用户创建简单的技能 Markdown 说明文件,内容包括:技能名称、技能描述、技能标签、技能提示词等。
你是一个专业的技能创建助手,用于帮助用户创建或修改简单的技能 Markdown 说明文件,内容包括:技能名称、技能描述、技能标签、技能提示词等。

{% if existing_skill %}
## 修改存量技能模式

用户正在修改存量技能,请参考以下存量技能内容,并结合用户的新需求,综合生成新的技能内容。

### 存量技能信息

**技能名称**: {{ existing_skill.name }}
**技能描述**: {{ existing_skill.description }}
**技能标签**: {{ existing_skill.tags | join(', ') if existing_skill.tags else '无' }}

### 存量技能内容

```
{{ existing_skill.content }}
```

### 修改指导原则

1. **保留有价值部分**:如果存量技能的功能仍然有效,保留其核心逻辑
2. **整合新需求**:将用户新增或修改的需求整合到技能内容中
3. **优化而非重建**:在现有基础上优化,而非重新创建

{% else %}
## 工作流程

根据用户请求,直接生成技能内容并输出。**不要分步骤执行**,直接整合所有内容返回。

{% endif %}
## 输出格式

**重要**:所有需要写入 SKILL.md 的内容必须用 `<SKILL>` 和 `</SKILL>` XML 分隔符包裹。
Expand Down Expand Up @@ -52,9 +77,19 @@ system_prompt: |-
- **不要**在路径中使用 Windows 风格的反斜杠。

user_prompt: |-
{% if existing_skill %}
请帮我修改存量技能「{{ existing_skill.name }}」,需求如下:

{{ user_request }}

**重要**:请参考上述存量技能内容,结合用户的新需求,综合生成新的技能内容。

{% else %}
请帮我创建一个技能,需求如下:

{{user_request}}
{{ user_request }}

{% endif %}

技能内容应该包括:
- name: 技能名称(使用英文或拼音,字母小写,单词用连字符分隔)
Expand All @@ -64,7 +99,7 @@ user_prompt: |-

**重要要求**:请严格按以下两个步骤进行:

**步骤 1**:生成 SKILL.md 内容并保存到文件
**步骤 1**:生成 SKILL.md 内容

**步骤 2**:生成简洁的总结作为最终回答(包括技能名称、功能亮点、适用场景)

Expand Down
43 changes: 36 additions & 7 deletions backend/utils/prompt_template_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import os
from typing import Dict, Any
from typing import Dict, Any, Optional

import yaml

Expand Down Expand Up @@ -150,18 +150,26 @@ def get_cluster_summary_reduce_prompt_template(language: str = LANGUAGE["ZH"]) -
return get_prompt_template('cluster_summary_reduce', language)


def get_skill_creation_simple_prompt_template(language: str = LANGUAGE["ZH"]) -> Dict[str, str]:
def get_skill_creation_simple_prompt_template(
language: str = LANGUAGE["ZH"],
existing_skill: Optional[Dict[str, Any]] = None
) -> Dict[str, str]:
"""
Get skill creation simple prompt template.
Get skill creation simple prompt template with Jinja2 rendering.

This template is now structured YAML with system_prompt and user_prompt sections.
This template is structured YAML with system_prompt and user_prompt sections.
Supports Jinja2 template syntax for dynamic content based on existing_skill.

Args:
language: Language code ('zh' or 'en')
existing_skill: Optional dict containing existing skill info for update scenarios.
Expected keys: name, description, tags, content

Returns:
Dict[str, str]: Template with keys 'system_prompt' and 'user_prompt'
Dict[str, str]: Template with keys 'system_prompt' and 'user_prompt', rendered with variables
"""
from jinja2 import Template

template_path_map = {
LANGUAGE["ZH"]: 'backend/prompts/skill_creation_simple_zh.yaml',
LANGUAGE["EN"]: 'backend/prompts/skill_creation_simple_en.yaml'
Expand All @@ -176,7 +184,28 @@ def get_skill_creation_simple_prompt_template(language: str = LANGUAGE["ZH"]) ->
with open(absolute_template_path, 'r', encoding='utf-8') as f:
template_data = yaml.safe_load(f)

# Prepare template context with existing_skill info
context = {
"existing_skill": existing_skill
}

# Render templates with Jinja2
system_prompt_raw = template_data.get("system_prompt", "")
user_prompt_raw = template_data.get("user_prompt", "")

try:
system_prompt = Template(system_prompt_raw).render(**context)
except Exception as e:
logger.warning(f"Failed to render system_prompt template: {e}, using raw content")
system_prompt = system_prompt_raw

try:
user_prompt = Template(user_prompt_raw).render(**context)
except Exception as e:
logger.warning(f"Failed to render user_prompt template: {e}, using raw content")
user_prompt = user_prompt_raw

return {
"system_prompt": template_data.get("system_prompt", ""),
"user_prompt": template_data.get("user_prompt", "")
"system_prompt": system_prompt,
"user_prompt": user_prompt
}
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,15 @@
: `用户需求:${currentInput}`;

await createSimpleSkillStream(
{ user_request: userPrompt },
{
user_request: userPrompt,
existing_skill: !isCreateMode ? {

Check warning on line 393 in frontend/app/[locale]/agents/components/agentConfig/SkillBuildModal.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ1sdLKp0UMIDiWzVef1&open=AZ1sdLKp0UMIDiWzVef1&pullRequest=2764
name: formValues.name || "",
description: formValues.description || "",
tags: formValues.tags || [],
content: formValues.content || "",
} : undefined,
},
{
onThinkingUpdate: (step, desc) => {
setThinkingStep(step);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { SkillGroup, Skill } from "@/types/agentConfig";
import { Tabs } from "antd";
import { Tabs, message } from "antd";
import { useAgentConfigStore } from "@/stores/agentConfigStore";
import { useSkillList } from "@/hooks/agent/useSkillList";
import { Info } from "lucide-react";
import { Info, Trash2 } from "lucide-react";
import { useConfirmModal } from "@/hooks/useConfirmModal";
import { deleteSkill } from "@/services/agentConfigService";
import SkillDetailModal from "./SkillDetailModal";

interface SkillManagementProps {
Expand All @@ -21,6 +23,7 @@ export default function SkillManagement({
currentAgentId,
}: SkillManagementProps) {
const { t } = useTranslation("common");
const { confirm } = useConfirmModal();

const currentAgentPermission = useAgentConfigStore(
(state) => state.currentAgentPermission
Expand All @@ -39,7 +42,7 @@ export default function SkillManagement({

const updateSkills = useAgentConfigStore((state) => state.updateSkills);

const { groupedSkills } = useSkillList();
const { groupedSkills, invalidate } = useSkillList();

const [activeTabKey, setActiveTabKey] = useState<string>("");
const [selectedSkill, setSelectedSkill] = useState<Skill | null>(null);
Expand Down Expand Up @@ -76,6 +79,25 @@ export default function SkillManagement({
setIsDetailModalOpen(true);
};

const handleDeleteClick = async (skill: Skill, e: React.MouseEvent) => {
e.stopPropagation();
confirm({
title: t("skillManagement.delete.confirmTitle"),
content: t("skillManagement.delete.confirmContent", { skillName: skill.name }),
okText: t("common.confirm"),
cancelText: t("common.cancel"),
onOk: async () => {
const result = await deleteSkill(skill.name);
if (result.success) {
message.success(t("skillManagement.delete.success"));
invalidate();
} else {
message.error(result.message || t("skillManagement.delete.failed"));
}
},
});
};

const tabItems = skillGroups.map((group) => {
const displayLabel =
group.label.length > 7
Expand Down Expand Up @@ -123,11 +145,18 @@ export default function SkillManagement({
<span className="font-medium text-gray-800 truncate">
{skill.name}
</span>
<Info
size={16}
className="flex-shrink-0 cursor-pointer text-gray-400 hover:text-gray-600 transition-colors"
onClick={(e) => handleInfoClick(skill, e)}
/>
<div className="flex items-center gap-2 flex-shrink-0">
<Info
size={16}
className="cursor-pointer text-gray-400 hover:text-gray-600 transition-colors"
onClick={(e) => handleInfoClick(skill, e)}
/>
<Trash2
size={16}
className="cursor-pointer text-gray-400 hover:text-red-500 transition-colors"
onClick={(e) => handleDeleteClick(skill, e)}
/>
</div>
</div>
);
})}
Expand Down
Loading
Loading