mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 01:18:05 +08:00
feat(skill): skill support
This commit is contained in:
@ -3,13 +3,14 @@ import logging
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from core.app.entities.app_asset_entities import AppAssetFileTree, AppAssetNode
|
||||
from core.app_assets.entities import (
|
||||
FileReference,
|
||||
SkillAsset,
|
||||
SkillMetadata,
|
||||
ToolReference,
|
||||
)
|
||||
from core.app_assets.entities.skill import FileReference, ToolConfiguration, ToolReference
|
||||
from core.app_assets.paths import AssetPaths
|
||||
from core.tools.entities.tool_entities import ToolProviderType
|
||||
from extensions.ext_storage import storage
|
||||
|
||||
from .base import AssetItemParser
|
||||
@ -26,10 +27,12 @@ class SkillAssetParser(AssetItemParser):
|
||||
tenant_id: str,
|
||||
app_id: str,
|
||||
assets_id: str,
|
||||
tree: AppAssetFileTree,
|
||||
) -> None:
|
||||
self._tenant_id = tenant_id
|
||||
self._app_id = app_id
|
||||
self._assets_id = assets_id
|
||||
self._tree = tree
|
||||
|
||||
def parse(
|
||||
self,
|
||||
@ -42,7 +45,7 @@ class SkillAssetParser(AssetItemParser):
|
||||
try:
|
||||
return self._parse_skill_asset(node_id, path, file_name, extension, storage_key)
|
||||
except Exception:
|
||||
logger.exception("Failed to parse skill asset %s: %s", node_id)
|
||||
logger.exception("Failed to parse skill asset %s", node_id)
|
||||
# handle as plain text
|
||||
return SkillAsset(
|
||||
node_id=node_id,
|
||||
@ -51,8 +54,6 @@ class SkillAssetParser(AssetItemParser):
|
||||
extension=extension,
|
||||
storage_key=storage_key,
|
||||
metadata=SkillMetadata(),
|
||||
tool_references=[],
|
||||
file_references=[],
|
||||
)
|
||||
|
||||
def _parse_skill_asset(
|
||||
@ -69,8 +70,6 @@ class SkillAssetParser(AssetItemParser):
|
||||
extension=extension,
|
||||
storage_key=storage_key,
|
||||
metadata=SkillMetadata(),
|
||||
tool_references=[],
|
||||
file_references=[],
|
||||
)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
@ -83,14 +82,13 @@ class SkillAssetParser(AssetItemParser):
|
||||
if not isinstance(content, str):
|
||||
raise ValueError(f"Skill document {node_id} 'content' must be a string")
|
||||
|
||||
metadata = SkillMetadata.model_validate(metadata_raw)
|
||||
|
||||
tool_references: list[ToolReference] = self._parse_tool_references(content)
|
||||
file_references: list[FileReference] = self._parse_file_references(content)
|
||||
|
||||
resolved_content = self._resolve_content(content, tool_references, file_references)
|
||||
resolved_key = AssetPaths.build_resolved_file(self._tenant_id, self._app_id, self._assets_id, node_id)
|
||||
storage.save(resolved_key, resolved_content.encode("utf-8"))
|
||||
current_file = self._tree.get(node_id)
|
||||
if current_file is None:
|
||||
raise ValueError(f"File not found for id={node_id}")
|
||||
|
||||
metadata = self._resolve_metadata(content, metadata_raw)
|
||||
storage.save(resolved_key, self._resolve_content(current_file, content, metadata).encode("utf-8"))
|
||||
|
||||
return SkillAsset(
|
||||
node_id=node_id,
|
||||
@ -99,48 +97,65 @@ class SkillAssetParser(AssetItemParser):
|
||||
extension=extension,
|
||||
storage_key=resolved_key,
|
||||
metadata=metadata,
|
||||
tool_references=tool_references,
|
||||
file_references=file_references,
|
||||
)
|
||||
|
||||
def _resolve_content(
|
||||
self,
|
||||
content: str,
|
||||
tool_references: list[ToolReference],
|
||||
file_references: list[FileReference],
|
||||
) -> str:
|
||||
for ref in tool_references:
|
||||
replacement = f"{ref.tool_name}"
|
||||
content = content.replace(ref.raw, replacement)
|
||||
|
||||
for ref in file_references:
|
||||
replacement = f"[file:{ref.uuid}]"
|
||||
content = content.replace(ref.raw, replacement)
|
||||
def _resolve_content(self, current_file: AppAssetNode, content: str, metadata: SkillMetadata) -> str:
|
||||
for match in FILE_REFERENCE_PATTERN.finditer(content):
|
||||
# replace with file relative path
|
||||
file_id = match.group(2)
|
||||
file = self._tree.get(file_id)
|
||||
if file is None:
|
||||
logger.warning("File not found for id=%s, skipping", file_id)
|
||||
# replace with file not found placeholder
|
||||
content = content.replace(match.group(0), "[File not found]")
|
||||
continue
|
||||
content = content.replace(match.group(0), self._tree.relative_path(current_file, file))
|
||||
|
||||
for match in TOOL_REFERENCE_PATTERN.finditer(content):
|
||||
tool_id = match.group(3)
|
||||
tool = metadata.tools.get(tool_id)
|
||||
if tool is None:
|
||||
logger.warning("Tool not found for id=%s, skipping", tool_id)
|
||||
# replace with tool not found placeholder
|
||||
content = content.replace(match.group(0), f"[Tool not found: {tool_id}]")
|
||||
continue
|
||||
content = content.replace(match.group(0), f"[Bash Command: {tool.tool_name}_{tool_id}]")
|
||||
return content
|
||||
|
||||
def _parse_tool_references(self, content: str) -> list[ToolReference]:
|
||||
tool_references: list[ToolReference] = []
|
||||
for match in TOOL_REFERENCE_PATTERN.finditer(content):
|
||||
tool_references.append(
|
||||
ToolReference(
|
||||
provider=match.group(1),
|
||||
tool_name=match.group(2),
|
||||
uuid=match.group(3),
|
||||
raw=match.group(0),
|
||||
)
|
||||
)
|
||||
|
||||
return tool_references
|
||||
|
||||
def _parse_file_references(self, content: str) -> list[FileReference]:
|
||||
def _resolve_file_references(self, content: str) -> list[FileReference]:
|
||||
file_references: list[FileReference] = []
|
||||
for match in FILE_REFERENCE_PATTERN.finditer(content):
|
||||
file_references.append(
|
||||
FileReference(
|
||||
source=match.group(1),
|
||||
uuid=match.group(2),
|
||||
raw=match.group(0),
|
||||
)
|
||||
)
|
||||
return file_references
|
||||
|
||||
def _resolve_tool_references(self, content: str, tools: dict[str, Any]) -> dict[str, ToolReference]:
|
||||
tool_references: dict[str, ToolReference] = {}
|
||||
for match in TOOL_REFERENCE_PATTERN.finditer(content):
|
||||
tool_id = match.group(3)
|
||||
tool_name = match.group(2)
|
||||
tool_provider = match.group(1)
|
||||
metadata = tools.get(tool_id)
|
||||
if metadata is None:
|
||||
raise ValueError(f"Tool metadata for {tool_id} not found")
|
||||
|
||||
configuration = ToolConfiguration.model_validate(metadata.get("configuration", {}))
|
||||
tool_references[tool_id] = ToolReference(
|
||||
uuid=tool_id,
|
||||
type=ToolProviderType.value_of(metadata.get("type", None)),
|
||||
provider=tool_provider,
|
||||
tool_name=tool_name,
|
||||
credential_id=metadata.get("credential_id", None),
|
||||
configuration=configuration,
|
||||
)
|
||||
return tool_references
|
||||
|
||||
def _resolve_metadata(self, content: str, metadata: dict[str, Any]) -> SkillMetadata:
|
||||
return SkillMetadata(
|
||||
files=self._resolve_file_references(content=content),
|
||||
tools=self._resolve_tool_references(content=content, tools=metadata.get("tools", {})),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user