Supersede draft PR #26 with a merge-safe prerequisite: bake and preload the amy, irina, and claude Piper models, route only validated server-side language to fixed voices, and leave the live voice deployment manifest unchanged. Remove the pinned WebUI speaker selector and its persisted preference, omit client voice fields from every outbound TTS path, and keep hands-free Voice Mode and the conversation instrument intact. Hostile or legacy voice fields remain ignored by the Piper server. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
471 lines
21 KiB
Python
471 lines
21 KiB
Python
"""Core configuration and workload contracts for Hermes chat."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import sys
|
|
from types import SimpleNamespace
|
|
|
|
import yaml
|
|
|
|
from testing.tests.test_hermes_chat_support import (
|
|
HERMES,
|
|
ROOT,
|
|
_documents,
|
|
)
|
|
|
|
def test_chat_config_enables_real_research_compute_and_delegation():
|
|
configmap = _documents(HERMES / "chat-configmap.yaml")[0]
|
|
config = yaml.safe_load(configmap["data"]["config.yaml"])
|
|
|
|
# AUTO leaves effort unset so the target chosen by Switchyard owns it.
|
|
assert "reasoning_effort" not in config["agent"]
|
|
assert config["model"]["model"] == "atlas/auto/fast"
|
|
assert config["web"] == {
|
|
"backend": "ddgs",
|
|
"search_backend": "ddgs",
|
|
"extract_backend": "public-extract",
|
|
}
|
|
assert config["delegation"]["max_concurrent_children"] == 2
|
|
assert config["delegation"]["max_iterations"] == 80
|
|
assert config["agent"]["max_turns"] == 120
|
|
assert config["tool_loop_guardrails"]["hard_stop_enabled"] is True
|
|
for platform in ("cli", "api_server"):
|
|
toolsets = config["platform_toolsets"][platform]
|
|
assert "delegation" in toolsets
|
|
assert "browser" in toolsets
|
|
assert "python_sandbox" in toolsets
|
|
assert "vision" in toolsets
|
|
assert "web" in toolsets
|
|
assert "image_gen" in toolsets
|
|
assert "terminal" not in toolsets
|
|
assert "code_execution" not in toolsets
|
|
|
|
|
|
def test_agent_config_keeps_delegated_reviewers_from_owning_task_lifecycle():
|
|
configmap = _documents(HERMES / "agent-configmap.yaml")[0]
|
|
instructions = configmap["data"]["AGENTS.md"]
|
|
soul = configmap["data"]["SOUL.md"]
|
|
config = yaml.safe_load(configmap["data"]["config.yaml"])
|
|
|
|
assert (
|
|
"Only the foreground durable worker owns its Kanban task lifecycle"
|
|
in instructions
|
|
)
|
|
assert "Never unblock and then complete" in instructions
|
|
assert "gateway dispatcher can claim the transient" in instructions
|
|
for platform in ("cli", "api_server"):
|
|
assert "kanban" in config["platform_toolsets"][platform]
|
|
assert "must never complete, block, unblock, reclaim" in instructions
|
|
assert "task's final structured result itself" in instructions
|
|
assert "Atlas organization has private visibility" in instructions
|
|
assert "may be public or private" in instructions
|
|
assert "do not infer a\nrepository's visibility" in instructions
|
|
assert "brokered Git for clone, fetch" in instructions
|
|
assert "client carries no repository credential" in instructions
|
|
assert "Never call `kanban_show` without a known, non-empty task ID" in instructions
|
|
assert "bounded ad-hoc inspection and acceptance checks may" in instructions
|
|
assert "load implementation or TDD skills" in instructions
|
|
assert "Never call `kanban_show` without\na known, non-empty task ID" in soul
|
|
assert "must load a skill only when its workflow\nmaterially applies" in soul
|
|
assert "no repository token is present in this pod" in soul
|
|
assert "`scm.bstein.dev` is Forgejo/Gitea, not GitHub" in instructions
|
|
assert "GitHub/`gh` skill for an Atlas remote" in instructions
|
|
assert "load\n`$manage-atlas-pull-requests`" in instructions
|
|
assert "Updates, merge,\napprove, close, delete, comments" in instructions
|
|
assert "Leave every PR\nunmerged for Brad's review" in instructions
|
|
assert "Use the Gitea pull-request merge endpoint" not in instructions
|
|
assert "reconcile the open PR" not in instructions
|
|
assert "use Flux reconciliation after" not in instructions
|
|
assert "PR publication ends at the verified open draft" in instructions
|
|
assert "JENKINS_BASE_URL" in instructions
|
|
assert "Do not\nuse `git reset --hard`" in instructions
|
|
rendered = (HERMES / "agent-deployment.yaml").read_text()
|
|
assert 'git config --global user.name "Hermes Agent"' in rendered
|
|
assert 'git config --global user.email "hermes@bstein.dev"' in rendered
|
|
|
|
|
|
def test_agent_image_completes_parked_kanban_tasks_atomically():
|
|
dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-agent").read_text()
|
|
|
|
assert (
|
|
"AND status IN ('running', 'ready', 'blocked', 'scheduled')"
|
|
in dockerfile
|
|
)
|
|
assert (
|
|
"Atomically mark running, ready, blocked, or scheduled tasks done"
|
|
in dockerfile
|
|
)
|
|
assert "kind IN ('created', 'blocked', 'unblocked')" in dockerfile
|
|
assert "payload.get(\"status\") == \"blocked\"" in dockerfile
|
|
assert "hermes-kanban-blocked-regression.py" in dockerfile
|
|
|
|
|
|
def test_sandbox_shares_only_the_tenant_workspace_without_credentials():
|
|
sandbox_docs = _documents(HERMES / "chat-sandbox.yaml")
|
|
deployments = [doc for doc in sandbox_docs if doc["kind"] == "Deployment"]
|
|
assert len(deployments) == 8
|
|
for ordinal, deployment in enumerate(deployments):
|
|
pod_spec = deployment["spec"]["template"]["spec"]
|
|
container = pod_spec["containers"][0]
|
|
assert pod_spec["automountServiceAccountToken"] is False
|
|
assert container["securityContext"]["readOnlyRootFilesystem"] is True
|
|
assert container["securityContext"]["runAsNonRoot"] is True
|
|
assert container["securityContext"]["runAsGroup"] == 10000
|
|
assert not container.get("env")
|
|
assert {mount["mountPath"] for mount in container["volumeMounts"]} == {
|
|
"/tmp",
|
|
"/workspace",
|
|
"/opt/data/workspace",
|
|
}
|
|
workspace_volume = next(
|
|
item for item in pod_spec["volumes"] if item["name"] == "workspace"
|
|
)
|
|
assert workspace_volume["persistentVolumeClaim"]["claimName"] == (
|
|
f"workspace-hermes-chat-tenant-{ordinal}"
|
|
)
|
|
|
|
statefulset = _documents(HERMES / "chat-statefulset.yaml")[0]
|
|
templates = statefulset["spec"]["volumeClaimTemplates"]
|
|
workspace = next(item for item in templates if item["metadata"]["name"] == "workspace")
|
|
assert workspace["spec"]["resources"]["requests"]["storage"] == "10Gi"
|
|
assert workspace["spec"]["accessModes"] == ["ReadWriteMany"]
|
|
|
|
pod_spec = statefulset["spec"]["template"]["spec"]
|
|
hermes = next(item for item in pod_spec["containers"] if item["name"] == "hermes")
|
|
startup = hermes["args"][0]
|
|
assert "hermes-chat-sandbox-${ordinal}.hermes-chat-sandbox" in startup
|
|
assert any(
|
|
mount["name"] == "workspace" and mount["mountPath"] == "/opt/data/workspace"
|
|
for mount in hermes["volumeMounts"]
|
|
)
|
|
|
|
policies = _documents(HERMES / "networkpolicy.yaml")
|
|
deny = next(
|
|
item
|
|
for item in policies
|
|
if item["metadata"]["name"] == "hermes-chat-sandbox-deny"
|
|
)
|
|
assert deny["spec"]["ingress"] == []
|
|
assert deny["spec"]["egress"] == []
|
|
for ordinal in range(4):
|
|
policy = next(
|
|
item
|
|
for item in policies
|
|
if item["metadata"]["name"] == f"hermes-chat-sandbox-tenant-{ordinal}"
|
|
)
|
|
assert policy["spec"]["podSelector"]["matchLabels"][
|
|
"ai.bstein.dev/tenant-ordinal"
|
|
] == str(ordinal)
|
|
source = policy["spec"]["ingress"][0]["from"][0]["podSelector"][
|
|
"matchLabels"
|
|
]
|
|
assert source["statefulset.kubernetes.io/pod-name"] == (
|
|
f"hermes-chat-tenant-{ordinal}"
|
|
)
|
|
|
|
|
|
def test_gateway_image_honors_ui_model_and_caps_reasoning():
|
|
dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-agent").read_text()
|
|
assert (
|
|
"FROM nousresearch/hermes-agent@sha256:"
|
|
"47d4bd4cc420b70e40ed75efdade373e45b86b7382d4013a054208982bb6ba08"
|
|
) in dockerfile
|
|
assert "_resolve_request_route" in dockerfile
|
|
assert 'allowed_providers = {"atlas-switchyard"}' in dockerfile
|
|
assert 'reasoning_effort=body.get("reasoning_effort")' in dockerfile
|
|
assert 'reasoning_config = {"enabled": True, "effort": "xhigh"}' in dockerfile
|
|
assert "ddgs==9.14.4" in dockerfile
|
|
assert "specific not in _LEGACY_WEB_BACKENDS" in dockerfile
|
|
assert '"pre_internal_route"' in dockerfile
|
|
assert "pre_internal_route hook failed" in dockerfile
|
|
assert '"pre_subagent_route"' in dockerfile
|
|
assert "pre_subagent_route hook failed" in dockerfile
|
|
assert "agent._hermes_explicit_model_pick = bool" in dockerfile
|
|
assert "agent._hermes_oneshot = True" in dockerfile
|
|
assert 'getattr(parent_agent, "_hermes_oneshot", False)' in dockerfile
|
|
assert 'var != "CLAUDE_CODE_OAUTH_TOKEN"' in dockerfile
|
|
assert '"source": "managed_claude_code_subscription"' in dockerfile
|
|
assert '"name": "Claude Code subscription (Switchyard)"' in dockerfile
|
|
assert "Anthropic API / direct OAuth (not used by Switchyard)" in dockerfile
|
|
assert "scheduleTerminalPaint" in dockerfile
|
|
assert "sessionTree.childrenByParent" in dockerfile
|
|
assert "SessionActivityPanel" in dockerfile
|
|
assert "include_children=${includeChildren}" in dockerfile
|
|
assert "_dashboard_session_is_active" in dockerfile
|
|
assert 'searchParams.get("lineage_root") || resumeParam' in dockerfile
|
|
assert "followActiveLineage" in dockerfile
|
|
assert 'next.delete("lineage_root")' in dockerfile
|
|
activity = (
|
|
ROOT / "dockerfiles" / "hermes-session-activity-panel.tsx"
|
|
).read_text()
|
|
assert "Live worker activity" in activity
|
|
assert "api.getSessionMessages" in activity
|
|
assert "aria-live=\"polite\"" in activity
|
|
assert "role=\"log\"" in activity
|
|
assert "aria-busy={running && messages.length === 0}" in activity
|
|
assert 'detail.source !== "api_server"' not in activity
|
|
assert "Poll-backed transcript; terminal rendering is not required." in activity
|
|
assert "DEFAULT_VISIBLE_MESSAGES = 250" in activity
|
|
assert "current + DEFAULT_VISIBLE_MESSAGES" in activity
|
|
assert 'Show up to{" "}' in activity
|
|
assert "Math.min(" in activity
|
|
assert "visibleMessages.map" in activity
|
|
assert "response.total_messages" in activity
|
|
assert "visibleLimit)," in activity
|
|
assert "function displayText(value: unknown)" in activity
|
|
assert "open && (" in activity
|
|
assert 'document.getElementById("hermes-resume-bootstrap")?.remove()' in activity
|
|
assert 'id="hermes-resume-bootstrap"' in dockerfile
|
|
assert 'role="status"' in dockerfile
|
|
assert 'aria-busy="true"' in dockerfile
|
|
assert "Opening Hermes worker activity…" in dockerfile
|
|
assert 'params.has("resume")' in dockerfile
|
|
assert "Open Telegram setup" in dockerfile
|
|
|
|
|
|
def test_telegram_router_local_build_defaults_to_cluster_architecture():
|
|
dockerfile = (
|
|
ROOT / "dockerfiles" / "Dockerfile.hermes-chat-router"
|
|
).read_text(encoding="utf-8")
|
|
|
|
assert "ARG TARGETOS=linux" in dockerfile
|
|
assert "ARG TARGETARCH=arm64" in dockerfile
|
|
assert "GOOS=${TARGETOS} GOARCH=${TARGETARCH}" in dockerfile
|
|
|
|
|
|
def test_chat_oauth_allows_stale_service_worker_retirement():
|
|
documents = _documents(HERMES / "oauth2-proxy.yaml")
|
|
deployment = next(
|
|
document
|
|
for document in documents
|
|
if document["kind"] == "Deployment"
|
|
and document["metadata"]["name"] == "oauth2-proxy-hermes-chat"
|
|
)
|
|
args = deployment["spec"]["template"]["spec"]["containers"][0]["args"]
|
|
|
|
assert "--skip-auth-route=GET=^/sw[.]js([?].*)?$" in args
|
|
assert "--custom-templates-dir=/etc/oauth2-proxy/templates" in args
|
|
|
|
template = (HERMES / "oauth2-proxy-templates" / "error.html").read_text()
|
|
assert 'http-equiv="refresh"' not in template
|
|
assert "Unable to find a valid CSRF token" in template
|
|
assert "expired or was already used" in template
|
|
assert "sessionStorage" in template
|
|
assert "Automatic recovery paused" in template
|
|
assert "/start?rd=/" in template
|
|
container = deployment["spec"]["template"]["spec"]["containers"][0]
|
|
assert "v7.15.3@sha256:10a1165743a192e" in container["image"]
|
|
assert "--cookie-csrf-per-request=true" in args
|
|
assert "--cookie-csrf-per-request-limit=8" in args
|
|
assert "--trusted-proxy-ip=10.42.0.0/16" in args
|
|
assert "--api-route=^/api/" in args
|
|
assert "--api-route=^/health$" in args
|
|
assert "--cookie-expire=168h" in args
|
|
assert "--cookie-refresh=19m" in args
|
|
assert "--session-store-type=redis" in args
|
|
assert any(
|
|
arg.startswith("--redis-connection-url=redis://hermes-oauth-sessions.")
|
|
for arg in args
|
|
)
|
|
|
|
agent = _documents(HERMES / "agent-deployment.yaml")[0]
|
|
agent_pod = agent["spec"]["template"]["spec"]
|
|
agent_oauth = next(
|
|
container for container in agent_pod["containers"]
|
|
if container["name"] == "oauth2-proxy"
|
|
)
|
|
assert "--custom-templates-dir=/etc/oauth2-proxy/templates" in agent_oauth["args"]
|
|
assert {
|
|
"name": "oauth-templates",
|
|
"mountPath": "/etc/oauth2-proxy/templates",
|
|
"readOnly": True,
|
|
} in agent_oauth["volumeMounts"]
|
|
agent_template_volume = next(
|
|
volume for volume in agent_pod["volumes"]
|
|
if volume["name"] == "oauth-templates"
|
|
)
|
|
assert agent_template_volume["configMap"]["name"] == (
|
|
"hermes-chat-oauth-templates"
|
|
)
|
|
|
|
|
|
def test_webui_recovers_auth_and_labels_session_scoped_controls():
|
|
dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-webui").read_text()
|
|
|
|
assert "res.status===401||res.status===403" in dockerfile
|
|
assert "window.location.assign('/oauth2/start?rd='" in dockerfile
|
|
assert "childrenExpanded?'▾ ':'▸ '" in dockerfile
|
|
assert "'atlas/auto/maximum': 'Automatic · Maximum'" in dockerfile
|
|
assert "hermes-webui-telegram-project-patch.py" in dockerfile
|
|
telegram_project_patch = (
|
|
ROOT / "dockerfiles" / "hermes-webui-telegram-project-patch.py"
|
|
).read_text()
|
|
assert "TELEGRAM_PROJECT_NAME = 'Telegram'" in telegram_project_patch
|
|
assert "session_key.startswith('telegram-topic-')" in telegram_project_patch
|
|
|
|
router = (ROOT / "dockerfiles" / "hermes-webui-router.js").read_text()
|
|
assert "'atlas/auto/fast':'Automatic · Fast'" in router
|
|
assert "'atlas/manual/codex/sol':'Codex · SOL'" in router
|
|
assert "'atlas/manual/claude/opus':'Claude · Opus'" in router
|
|
assert "watchModelOptions('modelSelect')" in router
|
|
assert "watchModelOptions('settingsModel')" in router
|
|
|
|
|
|
def test_chat_voice_uses_private_jetson_services_and_shared_auto_route():
|
|
statefulset = _documents(HERMES / "chat-statefulset.yaml")[0]
|
|
containers = statefulset["spec"]["template"]["spec"]["containers"]
|
|
webui = next(item for item in containers if item["name"] == "webui")
|
|
env = {item["name"]: item["value"] for item in webui["env"]}
|
|
|
|
assert env["HERMES_STT_URL"] == (
|
|
"http://hermes-stt.hermes.svc.cluster.local:9000/v1/audio/transcriptions"
|
|
)
|
|
assert env["HERMES_WEBUI_ATLAS_TTS_URL"] == (
|
|
"http://hermes-tts.hermes.svc.cluster.local:9001/v1/audio/speech"
|
|
)
|
|
assert "hermes_stt_client.py" in env["HERMES_LOCAL_STT_COMMAND"]
|
|
|
|
configmap = _documents(HERMES / "chat-configmap.yaml")[0]
|
|
config = yaml.safe_load(configmap["data"]["config.yaml"])
|
|
assert config["stt"] == {
|
|
"enabled": True,
|
|
"provider": "local_command",
|
|
"local": {"model": "small", "language": "auto"},
|
|
}
|
|
|
|
dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-webui").read_text()
|
|
assert "hermes-webui-atlas-patch.py" in dockerfile
|
|
assert "hermes-webui-atlas-voice.js" in dockerfile
|
|
voice_script = (ROOT / "dockerfiles" / "hermes-webui-atlas-voice.js").read_text()
|
|
assert "/api/transcribe/capability" in voice_script
|
|
assert "/api/transcribe" in voice_script
|
|
assert "/api/tts" in voice_script
|
|
assert "speakResponse(generation)" in voice_script
|
|
assert "window._splitForTTS(text,280)" in voice_script
|
|
assert "pending=fetchSpeech(chunks[index+1])" in voice_script
|
|
assert "restartSoon(token,450)" in voice_script
|
|
assert "constraints.voiceIsolation=true" in voice_script
|
|
assert "highpass.frequency.value=140" in voice_script
|
|
assert "Math.max(0.04,noiseFloor*2.4+0.006)" in voice_script
|
|
assert "while(preRoll.length>3) preRoll.shift()" in voice_script
|
|
|
|
stt_server = (ROOT / "dockerfiles" / "hermes-jetson-stt-server.py").read_text()
|
|
assert "def _repetitive_token" in stt_server
|
|
assert "compression_ratio_threshold=2.0" in stt_server
|
|
assert "no_speech_threshold=0.5" in stt_server
|
|
|
|
|
|
def test_voice_transcript_filter_removes_fan_hallucinations(monkeypatch):
|
|
server_path = ROOT / "dockerfiles" / "hermes-jetson-stt-server.py"
|
|
spec = importlib.util.spec_from_file_location("hermes_jetson_stt_server", server_path)
|
|
assert spec and spec.loader
|
|
module = importlib.util.module_from_spec(spec)
|
|
monkeypatch.setitem(sys.modules, "cgi", SimpleNamespace())
|
|
monkeypatch.setitem(
|
|
sys.modules,
|
|
"torch",
|
|
SimpleNamespace(cuda=SimpleNamespace(is_available=lambda: False)),
|
|
)
|
|
monkeypatch.setitem(sys.modules, "whisper", SimpleNamespace())
|
|
spec.loader.exec_module(module)
|
|
|
|
result = {
|
|
"segments": [
|
|
{
|
|
"text": " ththththththththth Testing.",
|
|
"no_speech_prob": 0.12,
|
|
"avg_logprob": -0.2,
|
|
},
|
|
{
|
|
"text": " background hum",
|
|
"no_speech_prob": 0.82,
|
|
"avg_logprob": -0.9,
|
|
},
|
|
]
|
|
}
|
|
|
|
assert module._clean_transcript(result) == "Testing."
|
|
|
|
|
|
def test_voice_models_are_baked_and_runtime_has_no_public_egress():
|
|
stt_dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-jetson-stt").read_text()
|
|
tts_dockerfile = (ROOT / "dockerfiles" / "Dockerfile.hermes-jetson-tts").read_text()
|
|
assert "ADD --checksum=sha256:aff26ae4" in stt_dockerfile
|
|
assert "ADD --checksum=sha256:9ecf7799" in stt_dockerfile
|
|
assert "--chmod=0444" in stt_dockerfile
|
|
assert "chmod 0555 /opt/models /opt/models/whisper" in stt_dockerfile
|
|
assert "HERMES_STT_CACHE=/opt/models/whisper" in stt_dockerfile
|
|
assert "ADD --checksum=sha256:4cabf7c3" in tts_dockerfile
|
|
assert "ADD --checksum=sha256:db42b97d" in tts_dockerfile
|
|
assert "ADD --checksum=sha256:b3a6e47b57b8c7fbe6a0ce2518161a50f59a9cdd8a50835c02cb02bdd6206c18" in tts_dockerfile
|
|
assert "ADD --checksum=sha256:95a23eb4d42909d38df73bb9ac7f45f597dbfcde2d1bf9526fdeaf5466977d77" in tts_dockerfile
|
|
assert "ADD --checksum=sha256:8ff38212d23da300bbe3705c645e6e5b9475f0bfde01558eb17813e22acaaaaa" in tts_dockerfile
|
|
assert "ADD --checksum=sha256:c2ec28bb38e2b59e93b959b3e40348c1afebbd272f30fed5d41205d08e98a9d7" in tts_dockerfile
|
|
assert "ADD --checksum=sha256:3ef40a71ea63852cd8ab7e6fa7d2ecdcfa67a0b47c9c48e3f10e02ee02083ea0" in tts_dockerfile
|
|
assert "ADD --checksum=sha256:1afc81f703c0e4cb3b4d7c0dca096b8b54a98806807f0170cf5eb5557723c12d" in tts_dockerfile
|
|
assert tts_dockerfile.count("--chmod=0444") == 12
|
|
assert "/opt/models/piper/en_US-amy-medium.onnx" in tts_dockerfile
|
|
assert "/opt/models/piper/ru_RU-irina-medium.onnx" in tts_dockerfile
|
|
assert "/opt/models/piper/es_MX-claude-high.onnx" in tts_dockerfile
|
|
assert "chmod 0555 /opt/models /opt/models/piper" in tts_dockerfile
|
|
assert "HERMES_TTS_CACHE=/opt/models/piper" in tts_dockerfile
|
|
assert "HERMES_TTS_VOICE=en_US-amy-medium" in tts_dockerfile
|
|
tts_server = (ROOT / "dockerfiles" / "hermes-jetson-tts-server.py").read_text()
|
|
assert "download_voice" not in tts_server
|
|
assert "baked Piper voice is missing" in tts_server
|
|
assert "session_options.intra_op_num_threads = threads" in tts_server
|
|
assert 'LANGUAGE_VOICE_MAP = {' in tts_server
|
|
assert '"en": "en_US-amy-medium"' in tts_server
|
|
assert '"ru": "ru_RU-irina-medium"' in tts_server
|
|
assert '"es": "es_MX-claude-high"' in tts_server
|
|
|
|
policies = _documents(HERMES / "networkpolicy.yaml")
|
|
voice_policy = next(
|
|
item for item in policies if item["metadata"]["name"] == "hermes-private-voice"
|
|
)
|
|
assert voice_policy["spec"]["policyTypes"] == ["Ingress", "Egress"]
|
|
assert not any(
|
|
"ipBlock" in destination
|
|
for rule in voice_policy["spec"]["egress"]
|
|
for destination in rule.get("to", [])
|
|
)
|
|
|
|
|
|
def test_voice_workloads_have_deliberate_xavier_placement():
|
|
documents = _documents(HERMES / "voice-deployment.yaml")
|
|
deployments = {
|
|
item["metadata"]["name"]: item
|
|
for item in documents
|
|
if item["kind"] == "Deployment"
|
|
}
|
|
stt = deployments["hermes-stt"]["spec"]["template"]["spec"]
|
|
tts = deployments["hermes-tts"]["spec"]["template"]["spec"]
|
|
|
|
assert "@sha256:" in stt["containers"][0]["image"]
|
|
assert "@sha256:" in tts["containers"][0]["image"]
|
|
assert stt["nodeSelector"] == {"kubernetes.io/hostname": "titan-21"}
|
|
assert tts["nodeSelector"] == {"kubernetes.io/hostname": "titan-21"}
|
|
assert stt["automountServiceAccountToken"] is False
|
|
assert tts["automountServiceAccountToken"] is False
|
|
assert stt["enableServiceLinks"] is False
|
|
assert tts["enableServiceLinks"] is False
|
|
assert stt["runtimeClassName"] == "nvidia"
|
|
assert stt["securityContext"]["supplementalGroups"] == [44]
|
|
stt_resources = stt["containers"][0]["resources"]
|
|
stt_env = {
|
|
item["name"]: item["value"] for item in stt["containers"][0]["env"]
|
|
}
|
|
assert stt_env["NVIDIA_DRIVER_CAPABILITIES"] == "compute,utility"
|
|
assert stt_resources["requests"]["nvidia.com/gpu.shared"] == 1
|
|
assert stt_resources["limits"]["nvidia.com/gpu.shared"] == 1
|
|
assert "nvidia.com/gpu.shared" not in tts["containers"][0]["resources"]["requests"]
|
|
tts_env = {
|
|
item["name"]: item["value"] for item in tts["containers"][0]["env"]
|
|
}
|
|
assert tts_env["HERMES_TTS_VOICE"] == "en_US-lessac-medium"
|
|
assert tts_env["HERMES_TTS_ONNX_THREADS"] == "2"
|
|
assert tts["containers"][0]["resources"]["limits"]["cpu"] == "4"
|
|
assert all("hostPath" not in volume for volume in stt["volumes"])
|
|
assert all("hostPath" not in volume for volume in tts["volumes"])
|