初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+180
View File
@@ -0,0 +1,180 @@
"""Skill 能力包服务(CRUD + 关键词触发匹配)。
CRUD 记审计;``match_active_skills`` 加载 enabled skills 调 matcher,返回 Skill ORM
列表(供 send_message 取 prompt_fragment 注入 contract)。
"""
from app.core.errors import AppError
from app.db.session import AsyncSessionLocal
from app.models.skills import Skill
from app.modules.skills.matcher import match_skills
from app.repositories import skills as skills_repo
from app.repositories.json_utils import dumps, loads_list
from app.schemas.skills import SkillResponse, ToolCapability
from app.services.audit import record_audit_event
async def create_skill(
*,
name: str,
description: str = "",
trigger: str = "",
prompt_fragment: str = "",
tool_bindings: list[str] | None = None,
risk_level: str = "L1",
status: str = "enabled",
) -> SkillResponse:
async with AsyncSessionLocal() as session:
existing = await skills_repo.get_skill_by_name(session, name)
if existing is not None:
raise AppError("conflict", f"Skill 名「{name}」已存在。")
skill = Skill(
name=name,
description=description,
trigger=trigger,
prompt_fragment=prompt_fragment,
tool_bindings_json=dumps(tool_bindings or []),
risk_level=risk_level,
status=status,
)
await skills_repo.add_skill(session, skill)
await record_audit_event(
session,
action="skill.created",
actor="user",
input_summary=name[:200],
resource_type="skill",
resource_id=skill.id,
)
await session.commit()
await session.refresh(skill)
return _to_response(skill)
async def get_skill(skill_id: str) -> SkillResponse | None:
async with AsyncSessionLocal() as session:
skill = await skills_repo.get_skill(session, skill_id)
return _to_response(skill) if skill else None
async def list_skills(*, status: str | None = None) -> list[SkillResponse]:
async with AsyncSessionLocal() as session:
skills = await skills_repo.list_skills(session, status=status)
return [_to_response(s) for s in skills]
async def list_all_capabilities(runtime) -> list[ToolCapability]:
"""统一列举 builtin + mcp(runtime.list_specs)+ skill(DB),含来源标记。"""
capabilities: list[ToolCapability] = []
if runtime is not None:
for spec in runtime.list_specs():
source = "mcp" if spec.name.startswith("mcp:") else "builtin"
capabilities.append(
ToolCapability(
name=spec.name,
description=spec.description,
source=source,
risk_level=spec.risk_level,
status="enabled",
)
)
for s in await list_skills(status="enabled"):
capabilities.append(
ToolCapability(
name=s.name,
description=s.description,
source="skill",
risk_level=s.risk_level,
status=s.status,
)
)
return capabilities
async def update_skill(
skill_id: str,
*,
description: str | None = None,
trigger: str | None = None,
prompt_fragment: str | None = None,
tool_bindings: list[str] | None = None,
risk_level: str | None = None,
status: str | None = None,
) -> SkillResponse:
async with AsyncSessionLocal() as session:
skill = await skills_repo.get_skill(session, skill_id)
if skill is None:
raise AppError("not_found", "Skill 不存在。")
if description is not None:
skill.description = description
if trigger is not None:
skill.trigger = trigger
if prompt_fragment is not None:
skill.prompt_fragment = prompt_fragment
if tool_bindings is not None:
skill.tool_bindings_json = dumps(tool_bindings)
if risk_level is not None:
skill.risk_level = risk_level
if status is not None:
skill.status = status
await record_audit_event(
session,
action="skill.updated",
actor="user",
input_summary=skill.name[:200],
resource_type="skill",
resource_id=skill.id,
)
await session.commit()
await session.refresh(skill)
return _to_response(skill)
async def delete_skill(skill_id: str) -> None:
async with AsyncSessionLocal() as session:
skill = await skills_repo.get_skill(session, skill_id)
if skill is None:
raise AppError("not_found", "Skill 不存在。")
name = skill.name
await session.delete(skill)
await record_audit_event(
session,
action="skill.deleted",
actor="user",
input_summary=name[:200],
resource_type="skill",
resource_id=skill_id,
)
await session.commit()
async def match_active_skills(context_text: str) -> list[Skill]:
"""加载 enabled skills,关键词匹配;返回命中的 Skill ORM(供注入 prompt_fragment)。"""
async with AsyncSessionLocal() as session:
skills = await skills_repo.list_skills(session, status="enabled")
return match_skills(context_text, skills)
async def get_enabled_skills_by_names(names: tuple[str, ...]) -> list[Skill]:
"""按名称返回 enabled Skills,供 Agent 显式引用。"""
wanted = set(names)
if not wanted:
return []
async with AsyncSessionLocal() as session:
skills = await skills_repo.list_skills(session, status="enabled")
return [skill for skill in skills if skill.name in wanted]
def _to_response(skill: Skill) -> SkillResponse:
return SkillResponse(
id=skill.id,
name=skill.name,
description=skill.description,
trigger=skill.trigger,
prompt_fragment=skill.prompt_fragment,
tool_bindings=loads_list(skill.tool_bindings_json),
risk_level=skill.risk_level,
status=skill.status,
created_at=skill.created_at,
updated_at=skill.updated_at,
)