oclaw/runtime/skills_prompt.py
oliver dbbe3add6a 重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。

Made-with: Cursor
2026-04-26 08:34:33 +08:00

145 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import json
import os
from typing import Any
from oclaw.runtime.skill_role_binding import (
allowed_workspace_skill_names_for_role,
should_apply_workspace_role_filter,
)
from oclaw.runtime.skills import discover_workspace_skill_manifests
from oclaw.runtime.tools.base import ToolRegistry
def _skill_runtime_enabled(store: Any) -> bool:
try:
raw_flag = str(store.get_setting("AIA_SKILL_RUNTIME_ENABLED") or "").strip().lower()
if raw_flag:
return raw_flag in {"1", "true", "yes", "on"}
except Exception:
pass
return True
def _skills_prompt_in_system_enabled(store: Any) -> bool:
raw = str(os.getenv("AIA_SKILLS_PROMPT_IN_SYSTEM") or "").strip().lower()
if raw in {"0", "false", "no", "off"}:
return False
try:
s = str(store.get_setting("AIA_SKILLS_PROMPT_IN_SYSTEM") or "").strip().lower()
if s in {"0", "false", "no", "off"}:
return False
except Exception:
pass
return True
def _disabled_skill_names(store: Any) -> set[str]:
try:
raw_disabled = str(store.get_setting("AIA_SKILL_DISABLED_NAMES") or "").strip()
if raw_disabled:
arr = json.loads(raw_disabled)
if isinstance(arr, list):
return {str(x).strip() for x in arr if str(x).strip()}
except Exception:
pass
return set()
def _max_skills_prompt_chars() -> int:
try:
return max(0, min(int(os.getenv("AIA_SKILLS_PROMPT_MAX_CHARS", "18000")), 500_000))
except Exception:
return 18_000
def escape_xml(s: str) -> str:
return (
str(s)
.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&apos;")
)
def collect_skill_catalog_entries(
*,
store: Any,
registry: ToolRegistry,
base_url: str,
skill_binding_role: str | None = None,
) -> list[tuple[str, str, str]]:
"""Return (name, description, location) for model-visible prompt skills."""
_ = registry
_ = base_url
disabled = _disabled_skill_names(store)
out: list[tuple[str, str, str]] = []
role_filter = should_apply_workspace_role_filter(store=store, skill_binding_role=skill_binding_role)
role_allow: set[str] = set()
if role_filter:
role_allow = allowed_workspace_skill_names_for_role(store=store, role=str(skill_binding_role or ""))
for m in sorted(discover_workspace_skill_manifests(), key=lambda x: x.name.lower()):
if m.disable_model_invocation or m.name in disabled:
continue
if role_filter:
if m.name not in role_allow:
continue
out.append((m.name, (m.description or "").strip() or m.name, m.skill_file))
return sorted(out, key=lambda x: x[0].lower())
def format_skills_for_prompt(entries: list[tuple[str, str, str]], *, max_chars: int) -> str:
"""Natural-language skill catalog block with a hard character budget."""
if not entries or max_chars <= 0:
return ""
def _render(subset: list[tuple[str, str, str]]) -> str:
lines: list[str] = ['']
lines.append("\n## 技能(skills):")
for name, desc, loc in subset:
safe_name = str(name).replace('"', '\\"')
safe_desc = str(desc).replace('"', '\\"')
safe_loc = str(loc).replace('"', '\\"')
lines.append(f'- name:"{safe_name}", description:"{safe_desc}", path:"{safe_loc}"')
return "\n".join(lines)
# Drop from the end until under budget (keep workspace-first order).
subset = list(entries)
while subset:
blob = _render(subset)
if len(blob) <= max_chars:
return blob
subset = subset[:-1]
return ""
def build_skills_catalog_block(
*,
store: Any,
registry: ToolRegistry,
base_url: str,
skill_binding_role: str | None = None,
) -> str:
if not _skill_runtime_enabled(store) or not _skills_prompt_in_system_enabled(store):
return ""
entries = collect_skill_catalog_entries(
store=store,
registry=registry,
base_url=base_url,
skill_binding_role=skill_binding_role,
)
return format_skills_for_prompt(entries, max_chars=_max_skills_prompt_chars())
__all__ = [
"build_skills_catalog_block",
"collect_skill_catalog_entries",
"escape_xml",
"format_skills_for_prompt",
]