import os from pathlib import Path from pydantic import BaseModel, Field from deerflow.config.runtime_paths import project_root, resolve_path DEFAULT_ES_QUERY_ROUTING_PROMPT = """当用户的问题需要基于 Elasticsearch 查询语句(Query DSL)灵活构造、调整或执行查询时,必须优先使用 `es_query` 技能。 典型场景包括:根据用户条件动态组合 bool/must/filter/should、范围、聚合、排序或分页等 ES 查询;或者用户直接提供、要求修改 ES 语句并据此查询数据。 执行前先读取 `es_query` 技能的 SKILL.md,严格按照技能中的流程和参数调用;不要用通用搜索替代,也不要凭记忆臆测查询结果。 仅当问题确实需要 ES 数据查询时使用;纯概念解释、普通代码说明或与 ES 无关的问题不要调用。""" class EsQueryRoutingConfig(BaseModel): """Prompt steering that routes flexible Elasticsearch queries to ``es_query``.""" enabled: bool = Field( default=False, description="Inject the es_query routing prompt into the agent system prompt.", ) prompt: str = Field( default=DEFAULT_ES_QUERY_ROUTING_PROMPT, description="Editable system-prompt instructions for deciding when and how to use es_query.", ) class SecurityScanConfig(BaseModel): """Toggle for the LLM-based skill security scanner.""" enabled: bool = Field( default=True, description=( "When false, skip the LLM security scan applied to skill installs, " "custom-skill edits, and rollbacks. The scan is then treated as 'allow'." ), ) class SkillsConfig(BaseModel): """Configuration for skills system""" use: str = Field( default="deerflow.skills.storage.local_skill_storage:LocalSkillStorage", description="Class path of the SkillStorage implementation.", ) path: str | None = Field( default=None, description="Path to skills directory. If not specified, defaults to skills under the caller project root.", ) container_path: str = Field( default="/mnt/skills", description="Path where skills are mounted in the sandbox container", ) security_scan: SecurityScanConfig = Field( default_factory=SecurityScanConfig, description="LLM security-scan toggle for skill content.", ) es_query_routing: EsQueryRoutingConfig = Field( default_factory=EsQueryRoutingConfig, description="Optional configurable routing prompt for the es_query skill.", ) def get_skills_path(self) -> Path: """ Get the resolved skills directory path. Returns: Path to the skills directory """ if self.path: # Use configured path (can be absolute or relative to project root) return resolve_path(self.path) if env_path := os.getenv("DEER_FLOW_SKILLS_PATH"): return resolve_path(env_path) return project_root() / "skills" def get_skill_container_path(self, skill_name: str, category: str = "public") -> str: """ Get the full container path for a specific skill. Args: skill_name: Name of the skill (directory name) category: Category of the skill (public or custom) Returns: Full path to the skill in the container """ return f"{self.container_path}/{category}/{skill_name}"