Skip to content

Commit bf66627

Browse files
committed
feat(skills): enhance skill retrieval by incorporating user context and app model in API endpoints
1 parent 506163a commit bf66627

File tree

2 files changed

+95
-32
lines changed

2 files changed

+95
-32
lines changed

api/controllers/console/app/skills.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from controllers.console import console_ns
44
from controllers.console.app.error import DraftWorkflowNotExist
55
from controllers.console.app.wraps import get_app_model
6-
from controllers.console.wraps import account_initialization_required, setup_required
6+
from controllers.console.wraps import account_initialization_required, current_account_with_tenant, setup_required
77
from libs.login import login_required
88
from models import App
99
from models.model import AppMode
@@ -33,13 +33,19 @@ def get(self, app_model: App, node_id: str):
3333
- tool_references: Aggregated tool references from all skill prompts
3434
- file_references: Aggregated file references from all skill prompts
3535
"""
36+
current_user, _ = current_account_with_tenant()
3637
workflow_service = WorkflowService()
3738
workflow = workflow_service.get_draft_workflow(app_model=app_model)
3839

3940
if not workflow:
4041
raise DraftWorkflowNotExist()
4142

42-
skill_info = SkillService.get_node_skill_info(workflow=workflow, node_id=node_id)
43+
skill_info = SkillService.get_node_skill_info(
44+
app=app_model,
45+
workflow=workflow,
46+
node_id=node_id,
47+
user_id=current_user.id,
48+
)
4349
return skill_info.model_dump()
4450

4551

@@ -62,11 +68,16 @@ def get(self, app_model: App):
6268
6369
Returns a list of nodes with their skill information.
6470
"""
71+
current_user, _ = current_account_with_tenant()
6572
workflow_service = WorkflowService()
6673
workflow = workflow_service.get_draft_workflow(app_model=app_model)
6774

6875
if not workflow:
6976
raise DraftWorkflowNotExist()
7077

71-
skills_info = SkillService.get_workflow_skills(workflow=workflow)
78+
skills_info = SkillService.get_workflow_skills(
79+
app=app_model,
80+
workflow=workflow,
81+
user_id=current_user.id,
82+
)
7283
return {"nodes": [info.model_dump() for info in skills_info]}

api/services/skill_service.py

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import logging
2+
from typing import Any
23

4+
from core.sandbox.entities.config import AppAssets
35
from core.skill.entities.api_entities import NodeSkillInfo
4-
from core.skill.entities.skill_metadata import ToolReference
5-
from core.skill.entities.tool_dependencies import ToolDependency
6+
from core.skill.entities.skill_document import SkillDocument
7+
from core.skill.entities.tool_dependencies import ToolDependencies, ToolDependency
8+
from core.skill.skill_compiler import SkillCompiler
9+
from core.skill.skill_manager import SkillManager
610
from core.workflow.enums import NodeType
11+
from models.model import App
712
from models.workflow import Workflow
13+
from services.app_asset_service import AppAssetService
814

915
logger = logging.getLogger(__name__)
1016

@@ -15,13 +21,15 @@ class SkillService:
1521
"""
1622

1723
@staticmethod
18-
def get_node_skill_info(workflow: Workflow, node_id: str) -> NodeSkillInfo:
24+
def get_node_skill_info(app: App, workflow: Workflow, node_id: str, user_id: str) -> NodeSkillInfo:
1925
"""
2026
Get skill information for a specific node in a workflow.
2127
2228
Args:
29+
app: The app model
2330
workflow: The workflow containing the node
2431
node_id: The ID of the node to get skill info for
32+
user_id: The user ID for asset access
2533
2634
Returns:
2735
NodeSkillInfo containing tool dependencies for the node
@@ -34,20 +42,26 @@ def get_node_skill_info(workflow: Workflow, node_id: str) -> NodeSkillInfo:
3442
if node_type != NodeType.LLM.value:
3543
return NodeSkillInfo(node_id=node_id)
3644

37-
tool_dependencies = SkillService._extract_tool_dependencies(node_data)
45+
# Check if node has any skill prompts
46+
if not SkillService._has_skill(node_data):
47+
return NodeSkillInfo(node_id=node_id)
48+
49+
tool_dependencies = SkillService._extract_tool_dependencies_with_compiler(app, node_data, user_id)
3850

3951
return NodeSkillInfo(
4052
node_id=node_id,
4153
tool_dependencies=tool_dependencies,
4254
)
4355

4456
@staticmethod
45-
def get_workflow_skills(workflow: Workflow) -> list[NodeSkillInfo]:
57+
def get_workflow_skills(app: App, workflow: Workflow, user_id: str) -> list[NodeSkillInfo]:
4658
"""
4759
Get skill information for all nodes in a workflow that have skill references.
4860
4961
Args:
62+
app: The app model
5063
workflow: The workflow to scan for skills
64+
user_id: The user ID for asset access
5165
5266
Returns:
5367
List of NodeSkillInfo for nodes that have skill references
@@ -56,10 +70,10 @@ def get_workflow_skills(workflow: Workflow) -> list[NodeSkillInfo]:
5670

5771
# Only scan LLM nodes since they're the only ones that support skills
5872
for node_id, node_data in workflow.walk_nodes(specific_node_type=NodeType.LLM):
59-
has_skill = SkillService._has_skill(node_data)
73+
has_skill = SkillService._has_skill(dict(node_data))
6074

6175
if has_skill:
62-
tool_dependencies = SkillService._extract_tool_dependencies(node_data)
76+
tool_dependencies = SkillService._extract_tool_dependencies_with_compiler(app, dict(node_data), user_id)
6377
result.append(
6478
NodeSkillInfo(
6579
node_id=node_id,
@@ -70,7 +84,7 @@ def get_workflow_skills(workflow: Workflow) -> list[NodeSkillInfo]:
7084
return result
7185

7286
@staticmethod
73-
def _has_skill(node_data: dict) -> bool:
87+
def _has_skill(node_data: dict[str, Any]) -> bool:
7488
"""Check if node has any skill prompts."""
7589
prompt_template = node_data.get("prompt_template", [])
7690
if isinstance(prompt_template, list):
@@ -80,29 +94,67 @@ def _has_skill(node_data: dict) -> bool:
8094
return False
8195

8296
@staticmethod
83-
def _extract_tool_dependencies(node_data: dict) -> list[ToolDependency]:
84-
"""Extract deduplicated tool dependencies from node data."""
85-
dependencies: dict[str, ToolDependency] = {}
97+
def _extract_tool_dependencies_with_compiler(
98+
app: App, node_data: dict[str, Any], user_id: str
99+
) -> list[ToolDependency]:
100+
"""Extract tool dependencies using SkillCompiler.
101+
102+
This method loads the SkillBundle and AppAssetFileTree, then uses
103+
SkillCompiler.compile_one() to properly extract tool dependencies
104+
including transitive dependencies from referenced skill files.
105+
"""
106+
# Get the draft assets to obtain assets_id and file_tree
107+
assets = AppAssetService.get_assets(
108+
tenant_id=app.tenant_id,
109+
app_id=app.id,
110+
user_id=user_id,
111+
is_draft=True,
112+
)
113+
114+
if not assets:
115+
logger.warning("No draft assets found for app_id=%s", app.id)
116+
return []
117+
118+
assets_id = assets.id
119+
file_tree = assets.asset_tree
120+
121+
# Load the skill bundle
122+
try:
123+
bundle = SkillManager.load_bundle(
124+
tenant_id=app.tenant_id,
125+
app_id=app.id,
126+
assets_id=assets_id,
127+
)
128+
except Exception as e:
129+
logger.debug("Failed to load skill bundle for app_id=%s: %s", app.id, e)
130+
# Return empty if bundle doesn't exist (no skills compiled yet)
131+
return []
132+
133+
# Compile each skill prompt and collect tool dependencies
134+
compiler = SkillCompiler()
135+
tool_deps_list: list[ToolDependencies] = []
86136

87137
prompt_template = node_data.get("prompt_template", [])
88138
if isinstance(prompt_template, list):
89139
for prompt in prompt_template:
90140
if isinstance(prompt, dict) and prompt.get("skill", False):
91-
metadata_dict = prompt.get("metadata") or {}
92-
tools_dict = metadata_dict.get("tools", {})
93-
94-
for uuid, tool_data in tools_dict.items():
95-
if isinstance(tool_data, dict):
96-
try:
97-
ref = ToolReference.model_validate({"uuid": uuid, **tool_data})
98-
key = f"{ref.provider}.{ref.tool_name}"
99-
if key not in dependencies:
100-
dependencies[key] = ToolDependency(
101-
type=ref.type,
102-
provider=ref.provider,
103-
tool_name=ref.tool_name,
104-
)
105-
except Exception:
106-
logger.debug("Skipping invalid tool reference: uuid=%s", uuid)
107-
108-
return list(dependencies.values())
141+
text: str = prompt.get("text", "")
142+
metadata: dict[str, Any] = prompt.get("metadata") or {}
143+
144+
skill_entry = compiler.compile_one(
145+
bundle=bundle,
146+
document=SkillDocument(skill_id="anonymous", content=text, metadata=metadata),
147+
file_tree=file_tree,
148+
base_path=AppAssets.PATH,
149+
)
150+
tool_deps_list.append(skill_entry.tools)
151+
152+
if not tool_deps_list:
153+
return []
154+
155+
# Merge all tool dependencies
156+
from functools import reduce
157+
158+
merged = reduce(lambda x, y: x.merge(y), tool_deps_list)
159+
160+
return merged.dependencies

0 commit comments

Comments
 (0)