11import threading
22import logging
3+ from typing import List
34from urllib .parse import urljoin
45from datetime import datetime
56
67from jinja2 import Template , StrictUndefined
7- from smolagents .utils import BASE_BUILTIN_MODULES
88from nexent .core .utils .observer import MessageObserver
99from nexent .core .agents .agent_model import AgentRunInfo , ModelConfig , AgentConfig , ToolConfig
1010from nexent .memory .memory_service import search_memory_in_levels
2727from utils .prompt_template_utils import get_agent_prompt_template
2828from utils .config_utils import tenant_config_manager , get_model_name_from_config
2929from consts .const import LOCAL_MCP_SERVER , MODEL_CONFIG_MAPPING , LANGUAGE , DATA_PROCESS_SERVICE
30+ import re
3031
3132logger = logging .getLogger ("create_agent_info" )
3233logger .setLevel (logging .DEBUG )
3334
3435
36+ def _get_skills_for_template (
37+ agent_id : int ,
38+ tenant_id : str ,
39+ version_no : int = 0
40+ ) -> List [dict ]:
41+ """Get skills list for prompt template injection.
42+
43+ Args:
44+ agent_id: Agent ID
45+ tenant_id: Tenant ID
46+ version_no: Version number
47+
48+ Returns:
49+ List of skill dicts with name and description
50+ """
51+ try :
52+ from services .skill_service import SkillService
53+ skill_service = SkillService ()
54+ enabled_skills = skill_service .get_enabled_skills_for_agent (
55+ agent_id = agent_id ,
56+ tenant_id = tenant_id ,
57+ version_no = version_no
58+ )
59+ return [
60+ {"name" : s .get ("name" , "" ), "description" : s .get ("description" , "" )}
61+ for s in enabled_skills
62+ ]
63+ except Exception as e :
64+ logger .warning (f"Failed to get skills for template: { e } " )
65+ return []
66+
67+
68+ def _get_skill_script_tools (
69+ agent_id : int ,
70+ tenant_id : str ,
71+ version_no : int = 0
72+ ) -> List [ToolConfig ]:
73+ """Get tool config for skill script execution and skill reading.
74+
75+ Args:
76+ agent_id: Agent ID for filtering available skills in error messages.
77+ tenant_id: Tenant ID for filtering available skills in error messages.
78+ version_no: Version number for filtering available skills.
79+
80+ Returns:
81+ List of ToolConfig for skill execution and reading tools
82+ """
83+ from consts .const import CONTAINER_SKILLS_PATH
84+
85+ skill_context = {
86+ "agent_id" : agent_id ,
87+ "tenant_id" : tenant_id ,
88+ "version_no" : version_no ,
89+ }
90+
91+ try :
92+ return [
93+ ToolConfig (
94+ class_name = "RunSkillScriptTool" ,
95+ name = "run_skill_script" ,
96+ description = "Execute a skill script with given parameters. Use this to run Python or shell scripts that are part of a skill." ,
97+ inputs = '{"skill_name": "str", "script_path": "str", "params": "dict"}' ,
98+ output_type = "string" ,
99+ params = {"local_skills_dir" : CONTAINER_SKILLS_PATH },
100+ source = "builtin" ,
101+ usage = "builtin" ,
102+ metadata = skill_context ,
103+ ),
104+ ToolConfig (
105+ class_name = "ReadSkillMdTool" ,
106+ name = "read_skill_md" ,
107+ description = "Read skill execution guide and optional additional files. Always reads SKILL.md first, then optionally reads additional files." ,
108+ inputs = '{"skill_name": "str", "additional_files": "list[str]"}' ,
109+ output_type = "string" ,
110+ params = {"local_skills_dir" : CONTAINER_SKILLS_PATH },
111+ source = "builtin" ,
112+ usage = "builtin" ,
113+ metadata = skill_context ,
114+ ),
115+ ToolConfig (
116+ class_name = "ReadSkillConfigTool" ,
117+ name = "read_skill_config" ,
118+ description = "Read the config.yaml file from a skill directory. Returns JSON containing configuration variables needed for skill workflows." ,
119+ inputs = '{"skill_name": "str"}' ,
120+ output_type = "string" ,
121+ params = {"local_skills_dir" : CONTAINER_SKILLS_PATH },
122+ source = "builtin" ,
123+ usage = "builtin" ,
124+ metadata = skill_context ,
125+ ),
126+ ToolConfig (
127+ class_name = "WriteSkillFileTool" ,
128+ name = "write_skill_file" ,
129+ description = "Write content to a file within a skill directory. Creates parent directories if they do not exist." ,
130+ inputs = '{"skill_name": "str", "file_path": "str", "content": "str"}' ,
131+ output_type = "string" ,
132+ params = {"local_skills_dir" : CONTAINER_SKILLS_PATH },
133+ source = "builtin" ,
134+ usage = "builtin" ,
135+ metadata = skill_context ,
136+ )
137+ ]
138+ except Exception as e :
139+ logger .warning (f"Failed to load skill script tool: { e } " )
140+ return []
141+
142+
35143async def create_model_config_list (tenant_id ):
36144 records = get_model_records ({"model_type" : "llm" }, tenant_id )
37145 model_list = []
@@ -169,22 +277,26 @@ async def create_agent_config(
169277 logger .error (f"Failed to build knowledge base summary: { e } " )
170278
171279 # Assemble system_prompt
172- if duty_prompt or constraint_prompt or few_shots_prompt :
173- system_prompt = Template (prompt_template ["system_prompt" ], undefined = StrictUndefined ).render ({
174- "duty" : duty_prompt ,
175- "constraint" : constraint_prompt ,
176- "few_shots" : few_shots_prompt ,
177- "tools" : {tool .name : tool for tool in tool_list },
178- "managed_agents" : {agent .name : agent for agent in managed_agents },
179- "authorized_imports" : str (BASE_BUILTIN_MODULES ),
180- "APP_NAME" : app_name ,
181- "APP_DESCRIPTION" : app_description ,
182- "memory_list" : memory_list ,
183- "knowledge_base_summary" : knowledge_base_summary ,
184- "time" : datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
185- })
186- else :
187- system_prompt = agent_info .get ("prompt" , "" )
280+ # Get skills list for prompt template
281+ skills = _get_skills_for_template (agent_id , tenant_id , version_no )
282+
283+ render_kwargs = {
284+ "duty" : duty_prompt ,
285+ "constraint" : constraint_prompt ,
286+ "few_shots" : few_shots_prompt ,
287+ "tools" : {tool .name : tool for tool in tool_list },
288+ "skills" : skills ,
289+ "managed_agents" : {agent .name : agent for agent in managed_agents },
290+ "APP_NAME" : app_name ,
291+ "APP_DESCRIPTION" : app_description ,
292+ "memory_list" : memory_list ,
293+ "knowledge_base_summary" : knowledge_base_summary ,
294+ "time" : datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" ),
295+ "user_id" : user_id ,
296+ }
297+ system_prompt = Template (prompt_template ["system_prompt" ], undefined = StrictUndefined ).render (render_kwargs )
298+
299+ _print_prompt_with_token_count (system_prompt , agent_id , "BEFORE_INJECTION" )
188300
189301 if agent_info .get ("model_id" ) is not None :
190302 model_info = get_model_by_model_id (agent_info .get ("model_id" ))
@@ -197,9 +309,10 @@ async def create_agent_config(
197309 prompt_templates = await prepare_prompt_templates (
198310 is_manager = len (managed_agents ) > 0 ,
199311 system_prompt = system_prompt ,
200- language = language
312+ language = language ,
313+ agent_id = agent_id
201314 ),
202- tools = tool_list ,
315+ tools = tool_list + _get_skill_script_tools ( agent_id , tenant_id , version_no ) ,
203316 max_steps = agent_info .get ("max_steps" , 10 ),
204317 model_name = model_name ,
205318 provide_run_summary = agent_info .get ("provide_run_summary" , False ),
@@ -296,23 +409,46 @@ async def discover_langchain_tools():
296409 return langchain_tools
297410
298411
299- async def prepare_prompt_templates (is_manager : bool , system_prompt : str , language : str = 'zh' ):
412+ async def prepare_prompt_templates (
413+ is_manager : bool ,
414+ system_prompt : str ,
415+ language : str = 'zh' ,
416+ agent_id : int = None ,
417+ ):
300418 """
301419 Prepare prompt templates, support multiple languages
302420
303421 Args:
304422 is_manager: Whether it is a manager mode
305423 system_prompt: System prompt content
306424 language: Language code ('zh' or 'en')
425+ agent_id: Agent ID for fetching skill instances
307426
308427 Returns:
309428 dict: Prompt template configuration
310429 """
311430 prompt_templates = get_agent_prompt_template (is_manager , language )
312431 prompt_templates ["system_prompt" ] = system_prompt
432+
433+ # Print final prompt with all injections
434+ _print_prompt_with_token_count (prompt_templates ["system_prompt" ], agent_id , "FINAL_PROMPT" )
435+
313436 return prompt_templates
314437
315438
439+ def _print_prompt_with_token_count (prompt : str , agent_id : int = None , stage : str = "PROMPT" ):
440+ """Print prompt content and estimate token count using tiktoken."""
441+ try :
442+ import tiktoken
443+ encoding = tiktoken .get_encoding ("cl100k_base" )
444+ token_count = len (encoding .encode (prompt ))
445+ logger .info (f"[Skill Debug][{ stage } ] Agent { agent_id } token count: { token_count } " )
446+ logger .info (f"[Skill Debug][{ stage } ] Agent { agent_id } prompt:\n { prompt } " )
447+ except Exception as e :
448+ logger .warning (f"[Skill Debug][{ stage } ] Failed to count tokens: { e } " )
449+ logger .info (f"[Skill Debug][{ stage } ] Agent { agent_id } prompt:\n { prompt } " )
450+
451+
316452async def join_minio_file_description_to_query (minio_files , query ):
317453 final_query = query
318454 if minio_files and isinstance (minio_files , list ):
0 commit comments