feat: add skill prompt

This commit is contained in:
Javier Martinez
2026-06-12 20:48:10 +08:00
parent 548058115f
commit c36d27f008
4 changed files with 95 additions and 1 deletions

View File

@@ -109,6 +109,13 @@ class PromptConfig(BaseModel):
"available paths) when any code execution tool is present."
),
)
skills: bool = Field(
default=False,
description=(
"Enable skill management instructions (when to load/unload skills, "
"workflow guidance) when any skill management tool is present."
),
)
class System(BaseModel):

View File

@@ -554,6 +554,29 @@ class PromptBuilderService:
logger.warning("PromptBuilder: failed to render %s: %s", template_path, exc)
return PromptTemplate(template="")
def create_skills_prompt(
self,
tools: list["ToolSpec"],
few_shots: bool = False,
) -> BasePromptTemplate:
"""Create the skill management instructions prompt.
Renders ``chat/tools/skills.j2`` with the active skill tool namespace.
Returns an empty template when the template is missing or rendering fails.
"""
namespace = _build_tool_namespace(tools)
template_path = "chat/tools/skills.j2"
try:
template = self.template_service.get_template(template_path)
rendered = template.render(
namespace=namespace,
few_shots=str(few_shots),
)
return PromptTemplate(template=rendered.strip())
except Exception as exc:
logger.warning("PromptBuilder: failed to render %s: %s", template_path, exc)
return PromptTemplate(template="")
def create_thinking_guidelines(self, few_shots: bool = True) -> BasePromptTemplate:
"""Create the thinking/reasoning guidelines prompt.

View File

@@ -0,0 +1,30 @@
{% set list_tool = namespace.tools.get("list_skills_tool_name") %}
{% set load_tool = namespace.tools.get("load_skill_tool_name") %}
{% set unload_tool = namespace.tools.get("unload_skill_tool_name") %}
<skills>
Skills are curated instruction sets that give you specialised knowledge and workflows for specific domains. Use them whenever the user's request falls within a skill's area of expertise.
**When to use skills:**
- The user's request mentions a topic or task that a skill might cover.
- A task requires domain-specific steps, tools, or conventions you wouldn't know otherwise.
- The user explicitly asks you to use a skill.
**Workflow:**
1. Call `{{ list_tool }}` to see available skills and their descriptions.
2. Call `{{ load_tool }}` with the skill name that best matches the request.
3. Follow the instructions returned by `{{ load_tool }}` when completing the task.
4. Call `{{ unload_tool }}` when the task is done and the skill is no longer needed.
**Rules:**
- Always list skills before loading to confirm the name is correct.
- Load at most one skill at a time unless the task clearly spans multiple domains.
- If no skill matches the request, proceed without loading any.
- Never invent skill names; only use names returned by `{{ list_tool }}`.
{% if few_shots == "True" %}
Examples:
- User asks to "analyse this dataset using the data-science skill" → call `{{ list_tool }}`, then `{{ load_tool }}` with name `data-science`.
- User asks a general question unrelated to any skill → skip skill tools and answer directly.
- After finishing a skill-guided task → call `{{ unload_tool }}` with the skill name.
{% endif %}
</skills>

View File

@@ -19,7 +19,10 @@ from private_gpt.components.engines.chat_loop.models.chat_loop_phase import (
)
from private_gpt.components.engines.citations.types import Document
from private_gpt.components.prompts.prompt_builder import PromptBuilderService
from private_gpt.components.tools.tool_names import CODE_EXECUTION_INTERNAL_TOOLS
from private_gpt.components.tools.tool_names import (
CODE_EXECUTION_INTERNAL_TOOLS,
SKILL_MANAGEMENT_TOOLS,
)
if TYPE_CHECKING:
from private_gpt.chat.input_models import PromptConfig
@@ -31,6 +34,7 @@ _SOURCE_TOOL_INSTRUCTIONS = "platform:tool_instructions"
_SOURCE_CITATIONS = "platform:citations"
_SOURCE_THINKING = "platform:thinking"
_SOURCE_CODE_EXECUTION = "platform:code_execution"
_SOURCE_SKILLS = "platform:skills"
_ALL_PLATFORM_SOURCES = frozenset(
{
@@ -38,6 +42,7 @@ _ALL_PLATFORM_SOURCES = frozenset(
_SOURCE_CITATIONS,
_SOURCE_THINKING,
_SOURCE_CODE_EXECUTION,
_SOURCE_SKILLS,
}
)
@@ -86,6 +91,10 @@ class PlatformGuidelinesInterceptor(ChatRequestLoopInterceptor):
if prompt.code_execution and self._has_code_execution_tool(tools):
stack = self._inject_code_execution(stack, tools)
# 5. Skill management instructions
if prompt.skills and self._has_skill_management_tool(tools):
stack = self._inject_skills(stack, tools)
state.input.context_stack = stack
context.set_state(state)
@@ -154,6 +163,20 @@ class PlatformGuidelinesInterceptor(ChatRequestLoopInterceptor):
)
return stack
def _inject_skills(
self, stack: ContextStack, tools: list[ToolSpec]
) -> ContextStack:
content = self._prompt_builder.create_skills_prompt(tools).format()
if content:
stack = stack.append_layer(
ToolInstructionsLayer(
tool_name="skills",
instructions=content,
source=_SOURCE_SKILLS,
)
)
return stack
@staticmethod
def _has_code_execution_tool(tools: list[ToolSpec]) -> bool:
for tool in tools:
@@ -165,6 +188,17 @@ class PlatformGuidelinesInterceptor(ChatRequestLoopInterceptor):
return True
return False
@staticmethod
def _has_skill_management_tool(tools: list[ToolSpec]) -> bool:
for tool in tools:
try:
canonical = tool.get_original_tool_name()
except ValueError:
canonical = tool.name or ""
if canonical in SKILL_MANAGEMENT_TOOLS:
return True
return False
# ------------------------------------------------------------------
# Cached content loaders
# ------------------------------------------------------------------