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

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

167 lines
6.1 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 threading
from typing import Any
from oclaw.runtime.memory_stage import render_memory_context_block
from oclaw.runtime.project_context_prompt import build_project_context_block
from oclaw.runtime.skills_prompt import build_skills_catalog_block
from oclaw.runtime.types import OclawMemoryContext
from oclaw.runtime.workspaces.experts import expert_workspace_signature_token
from oclaw.prompts.loader import render_runtime_prompt
from oclaw.runtime.tools.base import ToolRegistry
_EXECUTOR_STATIC_PROMPT_CACHE_LOCK = threading.Lock()
_EXECUTOR_STATIC_PROMPT_CACHE: dict[tuple[Any, ...], str] = {}
def _executor_prompt_settings_signature(store: Any) -> tuple[str, ...]:
keys = (
"AIA_SKILL_RUNTIME_ENABLED",
"AIA_SKILLS_PROMPT_IN_SYSTEM",
"AIA_SKILL_DISABLED_NAMES",
"AIA_SKILL_ROLE_BINDING_ENABLED",
"AIA_SKILL_ROLE_BINDING_MANAGER_INHERIT",
"AIA_PROJECT_CONTEXT_MAX_FILE_CHARS",
"AIA_PROJECT_CONTEXT_MAX_TOTAL_CHARS",
)
parts: list[str] = []
for key in keys:
try:
val = str(store.get_setting(key) or "")
except Exception:
val = ""
parts.append(f"{key}={val}")
return tuple(parts)
def _unified_skill_policy_guidance() -> str:
# Global policy for all agents (including dynamic/ephemeral) — appended to base_system in build_executor_system_prompt.
return (
"- 如果用户问你有哪些技能(skill/技能),请直接根据已注入的技能目录及其 description 回答。\n"
"- 不要为了“列出技能”而去读取 SKILL.md。只有在你确实需要某个技能的详细使用说明时,才读取对应 SKILL.md。\n"
"- 当你需要技能细节时,请按目录中给出的 path 读取对应的 SKILL.md。"
"- 技能包由说明文档和可选文件组成。运行时不会自动执行技能 `scripts/` 目录下的文件;\n"
"- internal hooks 是独立系统,也不会自动执行这些脚本。\n"
"- 当用户明确要求运行技能脚本时,请使用项目允许的 terminal/bash/exec(或同类)工具执行;\n"
"- 如果脚本依赖相对路径(例如 `.learnings/`),请将工作目录设置为用户工作区。\n"
"- 在没有显式工具调用成功结果前,不要假设脚本已经执行。\n"
"- 在 Windows 上,`.sh` 可能需要 Git Bash、WSL 或等效环境。\n"
)
def build_executor_system_prompt(
*,
store: Any,
tools: ToolRegistry | None,
base_url: str,
base_system: str,
memory_context: OclawMemoryContext | None,
lang: str,
workspace_dir: str | None = None,
skill_binding_role: str | None = None,
) -> str:
"""Build the final system string for the oclaw executor (memory block + skills catalog).
`lang` is reserved for future localized system fragments; current templates are bilingual/static.
"""
_ = lang # reserved for i18n extensions
final_system = get_executor_prompt_static(
store=store,
tools=tools,
base_url=base_url,
base_system=base_system,
workspace_dir=workspace_dir,
skill_binding_role=skill_binding_role,
)
mem_block = render_memory_context_block(memory_context or OclawMemoryContext())
if not mem_block:
return final_system
return render_runtime_prompt(
"runtime/system_with_memory.md",
variables={"system_prompt": final_system, "memory_context": mem_block},
strict=True,
)
def get_executor_prompt_static(
*,
store: Any,
tools: ToolRegistry | None,
base_url: str,
base_system: str,
workspace_dir: str | None = None,
skill_binding_role: str | None = None,
) -> str:
cache_key = (
str(base_url or "").strip(),
str(base_system or "").strip(),
str(workspace_dir or "").strip(),
str(skill_binding_role or "").strip().lower(),
expert_workspace_signature_token(),
_executor_prompt_settings_signature(store),
bool(tools is not None),
)
with _EXECUTOR_STATIC_PROMPT_CACHE_LOCK:
cached = _EXECUTOR_STATIC_PROMPT_CACHE.get(cache_key)
if isinstance(cached, str):
return cached
final_system = str(base_system or "").strip()
guide = _unified_skill_policy_guidance().strip()
if guide and guide not in final_system:
final_system = f"{final_system}\n\n{guide}".strip()
project_block = build_project_context_block(store=store, workspace_dir=workspace_dir)
if project_block:
final_system = f"{final_system}\n\n{project_block}".strip()
if tools is not None:
cat = build_skills_catalog_block(
store=store,
registry=tools,
base_url=str(base_url or ""),
skill_binding_role=skill_binding_role,
)
if cat.strip():
final_system = render_runtime_prompt(
"runtime/system_with_skills.md",
variables={"system_body": final_system, "skills_catalog": cat},
strict=True,
)
with _EXECUTOR_STATIC_PROMPT_CACHE_LOCK:
_EXECUTOR_STATIC_PROMPT_CACHE[cache_key] = final_system
if len(_EXECUTOR_STATIC_PROMPT_CACHE) > 256:
_EXECUTOR_STATIC_PROMPT_CACHE.clear()
return final_system
def warm_executor_prompt_cache(
*,
store: Any,
tools: ToolRegistry | None,
base_url: str,
role_base_systems: dict[str, str],
workspace_dir: str | None = None,
) -> dict[str, int]:
warmed = 0
for role, base_system in (role_base_systems or {}).items():
_ = get_executor_prompt_static(
store=store,
tools=tools,
base_url=base_url,
base_system=str(base_system or ""),
workspace_dir=workspace_dir,
skill_binding_role=str(role or "").strip().lower() or None,
)
warmed += 1
return {"roles_warmed": int(warmed)}
def build_oclaw_executor_system_prompt(**kwargs: Any) -> str:
return build_executor_system_prompt(**kwargs)
__all__ = [
"build_executor_system_prompt",
"build_oclaw_executor_system_prompt",
"get_executor_prompt_static",
"warm_executor_prompt_cache",
]