Add global research verification policy

This commit is contained in:
Mikei386
2026-08-31 22:35:09 +02:00
parent 82a1809423
commit b2ea53c383
5 changed files with 151 additions and 0 deletions
+10
View File
@@ -62,6 +62,16 @@ Qwen-Profil wird vom Profile Controller verwaltet.
Die verbindlichen Werte stehen in `config/profile-matrix.json` und
`docs/STANDARD_PROFILE_MATRIX.md`.
## Globale Modellrichtlinie
`config/global-system-policy.txt` wird vom Profile Router allen Textanfragen
über `/v1/chat/completions` und `/v1/responses` vorangestellt. Sie gilt damit
für alle Hermes-Profile und andere Clients, die den Athena-Router verwenden.
Der Router liest die Datei bei jeder Anfrage neu; spätere Textänderungen
benötigen nach der erstmaligen Bereitstellung keinen Container-Neustart.
Clients außerhalb des Routers und Werkzeuge, die ein Frontend nicht anbietet,
werden dadurch nicht erfasst.
## Werkzeuge
Portable Werkzeuge gehören auf Unraid in eigene, per DockerMan verwaltete
+2
View File
@@ -622,6 +622,7 @@ services:
tmpfs: ["/tmp:size=256m"]
volumes:
- ./router/router_profiles.json:/etc/mike-ai/router-profiles.json:ro
- ./config/global-system-policy.txt:/etc/mike-ai/global-system-policy.txt:ro
- router-state:/var/lib/mike-ai-profile-router
- router-images:/data/images
environment:
@@ -645,6 +646,7 @@ services:
# consume the complete context before yielding visible output.
MAX_GENERATION_TOKENS: "8192"
DEFAULT_REASONING_EFFORT: "${DEFAULT_REASONING_EFFORT:-off}"
GLOBAL_SYSTEM_POLICY_FILE: /etc/mike-ai/global-system-policy.txt
IMAGE_DIR: /data/images
IMAGE_WORKER_URL: http://image-worker:8086
IMAGE_WORKER_TOKEN: "${CONTROLLER_TOKEN:?CONTROLLER_TOKEN is required}"
+29
View File
@@ -0,0 +1,29 @@
## Mandatory Research and Verification Policy
When an answer, decision, or planned action depends on external facts and uncertainty could materially affect the result, verify the relevant information before proceeding.
Never infer the purpose or capabilities of an unfamiliar product, project, repository, application, container, image, service, package, or proper name from its name alone.
For unfamiliar software or services:
1. Inspect available local metadata such as the image name, labels, project URL, Compose file, package metadata, or README.
2. If its identity or capabilities remain unclear, use an available web, documentation, source-code, or research tool.
3. Base the answer on verified information and clearly distinguish facts from inference.
Research is required when:
- the information may have changed recently;
- you are unfamiliar with an error, parameter, API, feature, path, product, or technical procedure;
- compatibility, security, migration, or configuration details are unclear;
- your first or second reasonable attempt has failed;
- an incorrect assumption could cause damage, data loss, downtime, or significant wasted effort.
Prefer authoritative primary sources such as official documentation, upstream source code, release notes, specifications, and vendor documentation. Do not invent commands, parameters, endpoints, file paths, capabilities, or configuration options.
If no suitable research tool is available, clearly state what is uncertain. Ask the user before performing an action that could be harmful or difficult to reverse.
Do not perform unnecessary research when the answer can be derived reliably from information supplied by the user, local documentation, direct observation, or straightforward reasoning.
Never include passwords, API keys, tokens, private messages, confidential file contents, or other sensitive information in web searches or requests to external services.
Research is a verification mechanism, not a substitute for reasoning. Review the sources, reconcile conflicting information, and explain any remaining uncertainty.
+48
View File
@@ -26,6 +26,7 @@ from router_support import ( # noqa: E402
from ai_profile_router import ( # noqa: E402
_cap_chat_generation,
_context_matches,
_inject_global_system_policy,
_normalize_chat_image,
_normalize_chat_images,
_normalize_llamacpp_reasoning,
@@ -201,6 +202,53 @@ class ChatGenerationLimitTests(unittest.TestCase):
)
class GlobalSystemPolicyTests(unittest.TestCase):
def _inject(self, request: dict, path: str,
policy: str = "Verify facts.") -> dict:
with patch("ai_profile_router._load_global_system_policy",
return_value=policy):
return _inject_global_system_policy(request, path)
def test_chat_policy_precedes_existing_system_prompt(self) -> None:
request = {"messages": [
{"role": "system", "content": "Client policy."},
{"role": "user", "content": "Hello"},
]}
normalized = self._inject(request, "/v1/chat/completions")
self.assertEqual(
normalized["messages"][0]["content"],
"Verify facts.\n\nClient policy.",
)
def test_chat_policy_is_inserted_without_system_prompt(self) -> None:
request = {"messages": [{"role": "user", "content": "Hello"}]}
normalized = self._inject(request, "/v1/chat/completions")
self.assertEqual(normalized["messages"][0], {
"role": "system", "content": "Verify facts.",
})
def test_policy_is_not_duplicated(self) -> None:
request = {"messages": [{
"role": "system", "content": "Verify facts.\n\nClient policy.",
}]}
normalized = self._inject(request, "/v1/chat/completions")
self.assertEqual(
normalized["messages"][0]["content"].count("Verify facts."), 1)
def test_responses_policy_precedes_instructions(self) -> None:
request = {"instructions": "Client policy.", "input": "Hello"}
normalized = self._inject(request, "/v1/responses")
self.assertEqual(
normalized["instructions"],
"Verify facts.\n\nClient policy.",
)
def test_unrelated_endpoint_is_unchanged(self) -> None:
request = {"prompt": "Draw a cat"}
self.assertEqual(
self._inject(request, "/v1/images/generations"), request)
class RetentionTests(unittest.TestCase):
def test_oldest_pairs_are_removed(self) -> None:
with tempfile.TemporaryDirectory() as temp:
+62
View File
@@ -121,6 +121,8 @@ POLL_INTERVAL = float(os.environ.get("POLL_INTERVAL", "2")) # s, Polling
MAX_GENERATION_TOKENS = int(os.environ.get("MAX_GENERATION_TOKENS", "8192"))
DEFAULT_REASONING_EFFORT = os.environ.get(
"DEFAULT_REASONING_EFFORT", "off").strip().lower()
GLOBAL_SYSTEM_POLICY_FILE = os.environ.get(
"GLOBAL_SYSTEM_POLICY_FILE", "").strip()
# --- Bildgenerierung und Referenzbild-Bearbeitung (FLUX.2 Klein 4B) ---
LLAMA_SERVICE = os.environ.get("LLAMA_SERVICE", "mike-ai-llama-ui.service")
@@ -1270,6 +1272,57 @@ _REASONING_EFFORT_MAP = {
_REASONING_OFF = {"", "none", "off", "disabled", "false"}
def _load_global_system_policy() -> str:
"""Load the static cross-client platform policy.
The file is read for every request so operators can revise the policy
without rebuilding or restarting the router. Its contents remain stable
between edits and therefore remain friendly to upstream prompt caches.
"""
if not GLOBAL_SYSTEM_POLICY_FILE:
return ""
try:
with open(GLOBAL_SYSTEM_POLICY_FILE, encoding="utf-8") as handle:
return handle.read().strip()
except OSError as exc:
raise ValueError(
f"globale Systemrichtlinie nicht lesbar: {exc}") from exc
def _inject_global_system_policy(data: dict, path: str) -> dict:
"""Prepend the shared policy to OpenAI chat and Responses requests."""
policy = _load_global_system_policy()
if not policy:
return data
if path == "/v1/chat/completions":
messages = data.get("messages")
if not isinstance(messages, list):
return data
if messages and isinstance(messages[0], dict) and (
messages[0].get("role") == "system"
and isinstance(messages[0].get("content"), str)):
existing = messages[0]["content"]
if policy not in existing:
messages[0]["content"] = f"{policy}\n\n{existing}"
elif not any(
isinstance(message, dict)
and message.get("role") == "system"
and message.get("content") == policy
for message in messages):
messages.insert(0, {"role": "system", "content": policy})
return data
if path == "/v1/responses":
instructions = data.get("instructions")
if isinstance(instructions, str) and instructions:
if policy not in instructions:
data["instructions"] = f"{policy}\n\n{instructions}"
elif instructions is None or instructions == "":
data["instructions"] = policy
return data
def _normalize_llamacpp_reasoning(data: dict) -> dict:
"""Mappt OpenAI/Hermes-Reasoning auf llama.cpp-Template-Parameter.
@@ -2203,6 +2256,15 @@ class Handler(BaseHTTPRequestHandler):
"invalid_request_error", "unknown_model")
return
if path in {"/v1/chat/completions", "/v1/responses"}:
try:
data = _inject_global_system_policy(data, path)
except ValueError as exc:
self._send_error(500, str(exc), "server_error",
"system_policy_unavailable")
return
body = json.dumps(data).encode()
# Alle modellbezogenen Requests erhalten eine atomare Lease. Damit
# kann kein zweiter Client zwischen Profilwahl und Upstream-Request das
# Modell austauschen. Vision-Vorbereitung gehört zur selben Transaktion.