mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 07:20:44 +08:00
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
245 lines
8.5 KiB
Python
245 lines
8.5 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from oclaw.platform.llm.tool_schema import default_max_openai_tools_json_bytes
|
|
from oclaw.platform.llm.tool_wire_policy import (
|
|
load_merged_admin_config,
|
|
load_role_mode_for_role,
|
|
load_tool_policies_dict_for_role,
|
|
prepare_openai_tools_for_llm_api,
|
|
wire_graduation_effective,
|
|
)
|
|
from oclaw.runtime.tools.base import ToolSpec
|
|
from oclaw.runtime.tools.catalog import _is_truthy
|
|
from oclaw.runtime.tools.expert_registry import materialize_tools_for_expert, preview_expert_tools
|
|
from oclaw.runtime.tools.mcp.adapter import materialize_mcp_tools_for_specialist
|
|
from oclaw.runtime.tools.plugin_loader import materialize_plugin_tools
|
|
from oclaw.runtime.tools.public_registry import materialize_public_tools, preview_public_tools
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ToolExposurePlan:
|
|
role: str
|
|
base_url: str | None
|
|
max_json_bytes: int | None
|
|
mcp_enabled: bool
|
|
role_mode: str
|
|
wire_policy_effective: bool
|
|
policy_keys: int
|
|
public_risk_gate_allow_high: bool
|
|
public_blocked_high_risk_tools: list[str]
|
|
skipped_public: list[dict[str, Any]]
|
|
skipped_expert: list[dict[str, Any]]
|
|
tools_raw: list[dict[str, Any]]
|
|
tools_wired: list[dict[str, Any]]
|
|
removed_names: list[str]
|
|
removed_mcp_names: list[str]
|
|
changed_names: list[str]
|
|
added_names: list[str]
|
|
|
|
|
|
def _tool_names(tools: list[dict[str, Any]]) -> list[str]:
|
|
out: list[str] = []
|
|
for ent in tools or []:
|
|
if not isinstance(ent, dict) or str(ent.get("type") or "") != "function":
|
|
continue
|
|
fn = ent.get("function")
|
|
if not isinstance(fn, dict):
|
|
continue
|
|
nm = str(fn.get("name") or "").strip()
|
|
if nm:
|
|
out.append(nm)
|
|
return out
|
|
|
|
|
|
def _risk_gate_public_tools(tools: list[ToolSpec]) -> tuple[list[ToolSpec], bool, list[str]]:
|
|
allow_high = _is_truthy(os.getenv("AIA_PUBLIC_TOOLS_ALLOW_HIGH", "0"))
|
|
if allow_high:
|
|
return list(tools), True, []
|
|
kept: list[ToolSpec] = []
|
|
blocked: list[str] = []
|
|
for t in tools or []:
|
|
rl = str(getattr(t, "risk_level", "") or "low").strip().lower()
|
|
if rl == "high":
|
|
blocked.append(str(getattr(t, "name", "") or ""))
|
|
continue
|
|
kept.append(t)
|
|
return kept, False, sorted([x for x in blocked if x])
|
|
|
|
|
|
def build_internal_tool_specs(
|
|
*,
|
|
role: str,
|
|
preview: bool,
|
|
) -> tuple[list[ToolSpec], dict[str, Any]]:
|
|
"""Return internal ToolSpec list (public+expert) + diagnostics.
|
|
|
|
If preview=True, bypass caches and include skipped reasons.
|
|
"""
|
|
r = str(role or "").strip().lower()
|
|
pub_diag = preview_public_tools() if preview else {"tools": materialize_public_tools(), "skipped": []}
|
|
exp_diag = preview_expert_tools(r) if preview else {"tools": materialize_tools_for_expert(r), "skipped": []}
|
|
|
|
pub_tools = list(pub_diag.get("tools") or [])
|
|
pub_tools, allow_high, blocked = _risk_gate_public_tools(pub_tools)
|
|
exp_tools = list(exp_diag.get("tools") or [])
|
|
source_by_name: dict[str, str] = {}
|
|
|
|
merged: list[ToolSpec] = []
|
|
seen: set[str] = set()
|
|
for spec in list(pub_tools):
|
|
if not isinstance(spec, ToolSpec):
|
|
continue
|
|
nm = str(spec.name or "").strip()
|
|
if not nm or nm in seen:
|
|
continue
|
|
seen.add(nm)
|
|
merged.append(spec)
|
|
source_by_name[nm] = "public"
|
|
for spec in list(exp_tools):
|
|
if not isinstance(spec, ToolSpec):
|
|
continue
|
|
nm = str(spec.name or "").strip()
|
|
if not nm or nm in seen:
|
|
continue
|
|
seen.add(nm)
|
|
merged.append(spec)
|
|
source_by_name[nm] = "expert"
|
|
|
|
diag = {
|
|
"public_count": len(pub_tools),
|
|
"expert_count": len(exp_tools),
|
|
"merged_count": len(merged),
|
|
"public_risk_gate_allow_high": allow_high,
|
|
"public_blocked_high_risk_tools": blocked,
|
|
"skipped_public": list(pub_diag.get("skipped") or []),
|
|
"skipped_expert": list(exp_diag.get("skipped") or []),
|
|
"source_by_name": source_by_name,
|
|
}
|
|
plugin_rows = materialize_plugin_tools()
|
|
for row in plugin_rows:
|
|
spec = getattr(row, "tool", None)
|
|
if not isinstance(spec, ToolSpec):
|
|
continue
|
|
nm = str(spec.name or "").strip()
|
|
if not nm or nm in seen:
|
|
continue
|
|
seen.add(nm)
|
|
merged.append(spec)
|
|
source_by_name[nm] = "plugin"
|
|
diag["plugin_count"] = len(plugin_rows)
|
|
return merged, diag
|
|
|
|
|
|
def build_llm_tools_plan(
|
|
*,
|
|
store: Any,
|
|
role: str,
|
|
base_url: str | None,
|
|
max_json_bytes: int | None,
|
|
include_mcp: bool,
|
|
preview_internal: bool,
|
|
raw_openai_tools_override: list[dict[str, Any]] | None = None,
|
|
) -> ToolExposurePlan:
|
|
"""Plan raw and wired OpenAI tools for a role, with consistent policy semantics."""
|
|
r = str(role or "").strip().lower()
|
|
bu = str(base_url or "").strip() or None
|
|
cap = int(max_json_bytes) if isinstance(max_json_bytes, int) else None
|
|
if cap is None:
|
|
cap = default_max_openai_tools_json_bytes(bu)
|
|
if cap is not None and cap <= 0:
|
|
cap = None
|
|
|
|
internal_specs: list[ToolSpec] = []
|
|
diag_internal: dict[str, Any] = {
|
|
"public_risk_gate_allow_high": bool(_is_truthy(os.getenv("AIA_PUBLIC_TOOLS_ALLOW_HIGH", "0"))),
|
|
"public_blocked_high_risk_tools": [],
|
|
"skipped_public": [],
|
|
"skipped_expert": [],
|
|
}
|
|
if raw_openai_tools_override is None:
|
|
internal_specs, diag_internal = build_internal_tool_specs(role=r, preview=preview_internal)
|
|
|
|
mcp_enabled = bool(include_mcp) and (
|
|
_is_truthy(os.getenv("AIA_ENABLE_MCP_TOOLS")) or _is_truthy(os.getenv("OPS_ENABLE_MCP_TOOLS"))
|
|
)
|
|
mcp_specs = materialize_mcp_tools_for_specialist(store, specialist=r) if mcp_enabled else []
|
|
|
|
if raw_openai_tools_override is None:
|
|
raw_specs = list(internal_specs) + list(mcp_specs)
|
|
raw_openai_tools = [t.as_openai_tool() for t in raw_specs]
|
|
else:
|
|
raw_openai_tools = list(raw_openai_tools_override)
|
|
|
|
admin_cfg = load_merged_admin_config(store)
|
|
role_mode = load_role_mode_for_role(store, role=r)
|
|
policies = load_tool_policies_dict_for_role(store, role=r)
|
|
wire_effective = wire_graduation_effective(bu, admin_cfg) and role_mode == "restricted"
|
|
|
|
wired_openai_tools = prepare_openai_tools_for_llm_api(
|
|
raw_openai_tools,
|
|
base_url=bu,
|
|
max_json_bytes=cap,
|
|
store=store,
|
|
role=r,
|
|
)
|
|
|
|
raw_names = _tool_names(raw_openai_tools)
|
|
wired_names = _tool_names(wired_openai_tools)
|
|
raw_set = set(raw_names)
|
|
wired_set = set(wired_names)
|
|
removed = sorted([n for n in raw_set if n and n not in wired_set])
|
|
added = sorted([n for n in wired_set if n and n not in raw_set])
|
|
removed_mcp = [n for n in removed if n.startswith("mcp__")]
|
|
|
|
# changed = same tool name but payload differs
|
|
def _map_by_name(tools: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
m: dict[str, dict[str, Any]] = {}
|
|
for ent in tools or []:
|
|
if not isinstance(ent, dict) or str(ent.get("type") or "") != "function":
|
|
continue
|
|
fn = ent.get("function")
|
|
if not isinstance(fn, dict):
|
|
continue
|
|
nm = str(fn.get("name") or "").strip()
|
|
if nm:
|
|
m[nm] = fn
|
|
return m
|
|
|
|
raw_map = _map_by_name(raw_openai_tools)
|
|
wired_map = _map_by_name(wired_openai_tools)
|
|
changed: list[str] = []
|
|
for nm in sorted([n for n in wired_set if n in raw_set]):
|
|
if raw_map.get(nm) != wired_map.get(nm):
|
|
changed.append(nm)
|
|
|
|
return ToolExposurePlan(
|
|
role=r,
|
|
base_url=bu,
|
|
max_json_bytes=cap,
|
|
mcp_enabled=mcp_enabled,
|
|
role_mode=str(role_mode or "restricted"),
|
|
wire_policy_effective=bool(wire_effective),
|
|
policy_keys=len(policies),
|
|
public_risk_gate_allow_high=bool(diag_internal.get("public_risk_gate_allow_high")),
|
|
public_blocked_high_risk_tools=list(diag_internal.get("public_blocked_high_risk_tools") or []),
|
|
skipped_public=list(diag_internal.get("skipped_public") or []),
|
|
skipped_expert=list(diag_internal.get("skipped_expert") or []),
|
|
tools_raw=list(raw_openai_tools),
|
|
tools_wired=list(wired_openai_tools),
|
|
removed_names=removed,
|
|
removed_mcp_names=sorted(list(removed_mcp)),
|
|
changed_names=changed,
|
|
added_names=added,
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
"ToolExposurePlan",
|
|
"build_internal_tool_specs",
|
|
"build_llm_tools_plan",
|
|
]
|
|
|