Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
982335e
✨ Support skill framework in the backend
Jasonxia007 Mar 19, 2026
ab77fa4
✨ Support skill framework in the backend
Jasonxia007 Mar 20, 2026
9c1689f
✨ Add new skill file access RESTful API
Jasonxia007 Mar 25, 2026
b2f7c5b
✨ Add new skill file access RESTful API
Jasonxia007 Mar 25, 2026
6a799f1
✨ New builtin tools (write_skill_file, read_skill_config) available f…
Jasonxia007 Mar 26, 2026
d7cf442
feat: add skill management, tenant SkillList UI, and params DB/YAML h…
xuyaqist Mar 26, 2026
36bd301
✨ Frontend supports agent skill selection, creation and upload
Jasonxia007 Mar 26, 2026
80d02cf
✨ Frontend supports agent skill selection, creation and upload
Jasonxia007 Mar 26, 2026
b27d258
✨ Frontend typo error fixed
Jasonxia007 Mar 27, 2026
3651402
♻️ Remove duplicate api
Jasonxia007 Mar 27, 2026
98fa374
update skill params sql
xuyaqist Mar 27, 2026
d60ef9c
Merge remote-tracking branch 'origin/develop' into xyc/dev_skills
Jasonxia007 Mar 27, 2026
89d86ee
♻️ Merge redundant skill_repository into skill_db.py
Jasonxia007 Mar 27, 2026
ea667fb
add fetch skill list
xuyaqist Mar 27, 2026
253398b
Fetch newest config after save & Ensure the order of the form fields …
xuyaqist Mar 27, 2026
7b24a34
Merge remote-tracking branch 'origin/develop' into xyc/dev_skills
Jasonxia007 Mar 28, 2026
f34e13e
♻️ Add fallback regex matching logic when analyzing upload files
Jasonxia007 Mar 28, 2026
1797e39
🧪 Add test files
Jasonxia007 Mar 28, 2026
d464053
Merge remote-tracking branch 'origin/develop' into xyc/dev_skills
Jasonxia007 Mar 28, 2026
a8b0055
Update skill_app.py
Jasonxia007 Mar 28, 2026
861dde9
Update skill_app.py
Jasonxia007 Mar 28, 2026
8030e15
🧪 Add test files
Jasonxia007 Mar 28, 2026
3988f7e
🧪 Add test files
Jasonxia007 Mar 28, 2026
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
176 changes: 156 additions & 20 deletions backend/agents/create_agent_info.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import threading
import logging
from typing import List
from urllib.parse import urljoin
from datetime import datetime

from jinja2 import Template, StrictUndefined
from smolagents.utils import BASE_BUILTIN_MODULES
from nexent.core.utils.observer import MessageObserver
from nexent.core.agents.agent_model import AgentRunInfo, ModelConfig, AgentConfig, ToolConfig
from nexent.memory.memory_service import search_memory_in_levels
Expand All @@ -27,11 +27,119 @@
from utils.prompt_template_utils import get_agent_prompt_template
from utils.config_utils import tenant_config_manager, get_model_name_from_config
from consts.const import LOCAL_MCP_SERVER, MODEL_CONFIG_MAPPING, LANGUAGE, DATA_PROCESS_SERVICE
import re

logger = logging.getLogger("create_agent_info")
logger.setLevel(logging.DEBUG)


def _get_skills_for_template(
agent_id: int,
tenant_id: str,
version_no: int = 0
) -> List[dict]:
"""Get skills list for prompt template injection.

Args:
agent_id: Agent ID
tenant_id: Tenant ID
version_no: Version number

Returns:
List of skill dicts with name and description
"""
try:
from services.skill_service import SkillService
skill_service = SkillService()
enabled_skills = skill_service.get_enabled_skills_for_agent(
agent_id=agent_id,
tenant_id=tenant_id,
version_no=version_no
)
return [
{"name": s.get("name", ""), "description": s.get("description", "")}
for s in enabled_skills
]
except Exception as e:
logger.warning(f"Failed to get skills for template: {e}")
return []


def _get_skill_script_tools(
agent_id: int,
tenant_id: str,
version_no: int = 0
) -> List[ToolConfig]:
"""Get tool config for skill script execution and skill reading.

Args:
agent_id: Agent ID for filtering available skills in error messages.
tenant_id: Tenant ID for filtering available skills in error messages.
version_no: Version number for filtering available skills.

Returns:
List of ToolConfig for skill execution and reading tools
"""
from consts.const import CONTAINER_SKILLS_PATH

skill_context = {
"agent_id": agent_id,
"tenant_id": tenant_id,
"version_no": version_no,
}

try:
return [
ToolConfig(
class_name="RunSkillScriptTool",
name="run_skill_script",
description="Execute a skill script with given parameters. Use this to run Python or shell scripts that are part of a skill.",
inputs='{"skill_name": "str", "script_path": "str", "params": "dict"}',
output_type="string",
params={"local_skills_dir": CONTAINER_SKILLS_PATH},
source="builtin",
usage="builtin",
metadata=skill_context,
),
ToolConfig(
class_name="ReadSkillMdTool",
name="read_skill_md",
description="Read skill execution guide and optional additional files. Always reads SKILL.md first, then optionally reads additional files.",
inputs='{"skill_name": "str", "additional_files": "list[str]"}',
output_type="string",
params={"local_skills_dir": CONTAINER_SKILLS_PATH},
source="builtin",
usage="builtin",
metadata=skill_context,
),
ToolConfig(
class_name="ReadSkillConfigTool",
name="read_skill_config",
description="Read the config.yaml file from a skill directory. Returns JSON containing configuration variables needed for skill workflows.",
inputs='{"skill_name": "str"}',
output_type="string",
params={"local_skills_dir": CONTAINER_SKILLS_PATH},
source="builtin",
usage="builtin",
metadata=skill_context,
),
ToolConfig(
class_name="WriteSkillFileTool",
name="write_skill_file",
description="Write content to a file within a skill directory. Creates parent directories if they do not exist.",
inputs='{"skill_name": "str", "file_path": "str", "content": "str"}',
output_type="string",
params={"local_skills_dir": CONTAINER_SKILLS_PATH},
source="builtin",
usage="builtin",
metadata=skill_context,
)
]
except Exception as e:
logger.warning(f"Failed to load skill script tool: {e}")
return []


async def create_model_config_list(tenant_id):
records = get_model_records({"model_type": "llm"}, tenant_id)
model_list = []
Expand Down Expand Up @@ -169,22 +277,26 @@ async def create_agent_config(
logger.error(f"Failed to build knowledge base summary: {e}")

# Assemble system_prompt
if duty_prompt or constraint_prompt or few_shots_prompt:
system_prompt = Template(prompt_template["system_prompt"], undefined=StrictUndefined).render({
"duty": duty_prompt,
"constraint": constraint_prompt,
"few_shots": few_shots_prompt,
"tools": {tool.name: tool for tool in tool_list},
"managed_agents": {agent.name: agent for agent in managed_agents},
"authorized_imports": str(BASE_BUILTIN_MODULES),
"APP_NAME": app_name,
"APP_DESCRIPTION": app_description,
"memory_list": memory_list,
"knowledge_base_summary": knowledge_base_summary,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})
else:
system_prompt = agent_info.get("prompt", "")
# Get skills list for prompt template
skills = _get_skills_for_template(agent_id, tenant_id, version_no)

render_kwargs = {
"duty": duty_prompt,
"constraint": constraint_prompt,
"few_shots": few_shots_prompt,
"tools": {tool.name: tool for tool in tool_list},
"skills": skills,
"managed_agents": {agent.name: agent for agent in managed_agents},
"APP_NAME": app_name,
"APP_DESCRIPTION": app_description,
"memory_list": memory_list,
"knowledge_base_summary": knowledge_base_summary,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"user_id": user_id,
}
system_prompt = Template(prompt_template["system_prompt"], undefined=StrictUndefined).render(render_kwargs)

_print_prompt_with_token_count(system_prompt, agent_id, "BEFORE_INJECTION")

if agent_info.get("model_id") is not None:
model_info = get_model_by_model_id(agent_info.get("model_id"))
Expand All @@ -197,9 +309,10 @@ async def create_agent_config(
prompt_templates=await prepare_prompt_templates(
is_manager=len(managed_agents) > 0,
system_prompt=system_prompt,
language=language
language=language,
agent_id=agent_id
),
tools=tool_list,
tools=tool_list + _get_skill_script_tools(agent_id, tenant_id, version_no),
max_steps=agent_info.get("max_steps", 10),
model_name=model_name,
provide_run_summary=agent_info.get("provide_run_summary", False),
Expand Down Expand Up @@ -296,23 +409,46 @@ async def discover_langchain_tools():
return langchain_tools


async def prepare_prompt_templates(is_manager: bool, system_prompt: str, language: str = 'zh'):
async def prepare_prompt_templates(
is_manager: bool,
system_prompt: str,
language: str = 'zh',
agent_id: int = None,
):
"""
Prepare prompt templates, support multiple languages

Args:
is_manager: Whether it is a manager mode
system_prompt: System prompt content
language: Language code ('zh' or 'en')
agent_id: Agent ID for fetching skill instances

Returns:
dict: Prompt template configuration
"""
prompt_templates = get_agent_prompt_template(is_manager, language)
prompt_templates["system_prompt"] = system_prompt

# Print final prompt with all injections
_print_prompt_with_token_count(prompt_templates["system_prompt"], agent_id, "FINAL_PROMPT")

return prompt_templates


def _print_prompt_with_token_count(prompt: str, agent_id: int = None, stage: str = "PROMPT"):
"""Print prompt content and estimate token count using tiktoken."""
try:
import tiktoken
encoding = tiktoken.get_encoding("cl100k_base")
token_count = len(encoding.encode(prompt))
logger.info(f"[Skill Debug][{stage}] Agent {agent_id} token count: {token_count}")
logger.info(f"[Skill Debug][{stage}] Agent {agent_id} prompt:\n{prompt}")
except Exception as e:
logger.warning(f"[Skill Debug][{stage}] Failed to count tokens: {e}")
logger.info(f"[Skill Debug][{stage}] Agent {agent_id} prompt:\n{prompt}")


async def join_minio_file_description_to_query(minio_files, query):
final_query = query
if minio_files and isinstance(minio_files, list):
Expand Down
24 changes: 23 additions & 1 deletion backend/apps/agent_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
run_agent_stream,
stop_agent_tasks,
get_agent_call_relationship_impl,
clear_agent_new_mark_impl
clear_agent_new_mark_impl,
get_agent_by_name_impl,
)
from services.agent_version_service import (
publish_version_impl,
Expand Down Expand Up @@ -100,6 +101,27 @@ async def search_agent_info_api(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent search info error.")


@agent_config_router.get("/by-name/{agent_name}")
async def get_agent_by_name_api(
agent_name: str,
tenant_id: Optional[str] = Query(
None, description="Tenant ID for filtering (uses auth if not provided)"),
authorization: Optional[str] = Header(None)
):
"""
Look up an agent by name and return its agent_id and highest published version_no.
"""
try:
_, auth_tenant_id = get_current_user_id(authorization)
effective_tenant_id = tenant_id or auth_tenant_id
result = get_agent_by_name_impl(agent_name, effective_tenant_id)
return JSONResponse(status_code=HTTPStatus.OK, content=result)
except Exception as e:
logger.error(f"Agent by name lookup error: {str(e)}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent not found.")


@agent_config_router.get("/get_creating_sub_agent_id")
async def get_creating_sub_agent_info_api(authorization: Optional[str] = Header(None)):
"""
Expand Down
2 changes: 2 additions & 0 deletions backend/apps/config_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from apps.model_managment_app import router as model_manager_router
from apps.prompt_app import router as prompt_router
from apps.remote_mcp_app import router as remote_mcp_router
from apps.skill_app import router as skill_router
from apps.tenant_config_app import router as tenant_config_router
from apps.tool_config_app import router as tool_config_router
from apps.user_management_app import router as user_management_router
Expand Down Expand Up @@ -52,6 +53,7 @@

app.include_router(summary_router)
app.include_router(prompt_router)
app.include_router(skill_router)
app.include_router(tenant_config_router)
app.include_router(remote_mcp_router)
app.include_router(tenant_router)
Expand Down
Loading
Loading