Files
OpenJarvis/tests/core/test_config.py
T
05f2c02131 feat: Algolia DocSearch + learning subsystem reorganization (#43)
* chore: create learning subdirectory structure (routing, agents, intelligence)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: extract classify_query to routing/_utils.py

Move the classify_query() function and its regex patterns into a shared
utility module so multiple routing policies can import it without
depending on the full trace_policy module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move routing files to learning/routing/ subdirectory

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: create LearnedRouterPolicy merging trace-driven + SFT routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add conditional Algolia DocSearch integration

Add Algolia DocSearch as an optional search upgrade — native lunr.js
search remains the default until credentials are configured. Includes
CDN assets, Jinja2 conditional config injection, init script with
graceful fallback, light/dark theme CSS, improved search tokenization
for snake_case/dotted identifiers, and search boosts for key pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move agent_evolver and skill_discovery to learning/agents/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move learning/orchestrator to learning/intelligence/orchestrator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: delete removed learning policies, rewrite __init__.py, clean up api_routes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add SFT/GRPO/DSPy/GEPA config dataclasses, update LearningConfig

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add general-purpose SFT trainer (intelligence/sft_trainer.py)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update stale imports in multi_model_router example

Update imports to use new learning/routing/ paths after the
subdirectory reorganization. Replace BanditRouterPolicy with
LearnedRouterPolicy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add general-purpose GRPO trainer (intelligence/grpo_trainer.py)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add DSPy agent optimizer (agents/dspy_optimizer.py)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add GEPA agent optimizer (agents/gepa_optimizer.py)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add learning-dspy and learning-gepa optional dependency extras

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update integration test to check for learned policy instead of grpo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clean up stale APIs and unused params in examples

- deep_research: remove system_prompt and max_turns params not accepted
  by Jarvis.ask(), inline system prompt into the query instead
- doc_qa: remove unused --top-k CLI arg that was never passed to the API
- multi_model_router: fix select_model() call to match single-arg signature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: import SFT/GRPO trainers in intelligence/__init__.py for registry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove .md file changes from PR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: restore search boost frontmatter for key docs pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 21:34:31 -07:00

472 lines
16 KiB
Python

"""Tests for configuration, hardware detection, and engine recommendation."""
from __future__ import annotations
from pathlib import Path
from openjarvis.core.config import (
AgentConfig,
ChannelConfig,
EngineConfig,
GpuInfo,
HardwareInfo,
IntelligenceConfig,
JarvisConfig,
LearningConfig,
SandboxConfig,
SchedulerConfig,
SecurityConfig,
WhatsAppBaileysChannelConfig,
generate_default_toml,
load_config,
recommend_engine,
)
class TestDefaults:
def test_jarvis_config_defaults(self) -> None:
cfg = JarvisConfig()
assert cfg.engine.default == "ollama"
assert cfg.memory.default_backend == "sqlite"
assert cfg.telemetry.enabled is True
def test_engine_config_defaults(self) -> None:
ec = EngineConfig()
# Nested configs
assert ec.ollama.host == ""
assert ec.vllm.host == "http://localhost:8000"
assert ec.sglang.host == "http://localhost:30000"
assert ec.llamacpp.host == "http://localhost:8080"
assert ec.llamacpp.binary_path == ""
# Backward-compat properties still work
assert ec.ollama_host == ""
assert ec.vllm_host == "http://localhost:8000"
class TestRecommendEngine:
def test_no_gpu(self) -> None:
hw = HardwareInfo(platform="linux")
assert recommend_engine(hw) == "llamacpp"
def test_apple_silicon(self) -> None:
hw = HardwareInfo(
platform="darwin",
gpu=GpuInfo(vendor="apple", name="Apple M2 Max"),
)
assert recommend_engine(hw) == "mlx"
def test_nvidia_datacenter(self) -> None:
hw = HardwareInfo(
platform="linux",
gpu=GpuInfo(vendor="nvidia", name="NVIDIA A100-SXM4-80GB", vram_gb=80),
)
assert recommend_engine(hw) == "vllm"
def test_nvidia_consumer(self) -> None:
hw = HardwareInfo(
platform="linux",
gpu=GpuInfo(vendor="nvidia", name="NVIDIA GeForce RTX 4090", vram_gb=24),
)
assert recommend_engine(hw) == "ollama"
def test_amd(self) -> None:
hw = HardwareInfo(
platform="linux",
gpu=GpuInfo(vendor="amd", name="Radeon RX 7900 XTX"),
)
assert recommend_engine(hw) == "vllm"
class TestTomlLoading:
def test_load_missing_file_uses_defaults(self, tmp_path: Path) -> None:
cfg = load_config(tmp_path / "nonexistent.toml")
assert isinstance(cfg, JarvisConfig)
# engine default is derived from detected hardware — just ensure it's a string
assert isinstance(cfg.engine.default, str)
def test_load_overrides(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[engine]\ndefault = "vllm"\n\n[memory]\ndefault_backend = "faiss"\n'
)
cfg = load_config(toml_file)
assert cfg.engine.default == "vllm"
assert cfg.memory.default_backend == "faiss"
class TestGenerateToml:
def test_contains_engine_section(self) -> None:
hw = HardwareInfo(
platform="linux",
cpu_brand="Intel Xeon",
cpu_count=16,
ram_gb=64.0,
gpu=GpuInfo(vendor="nvidia", name="NVIDIA H100", vram_gb=80),
)
toml = generate_default_toml(hw)
assert "[engine]" in toml
assert 'default = "vllm"' in toml
assert "H100" in toml
class TestSecurityConfig:
def test_security_config_defaults(self) -> None:
sc = SecurityConfig()
assert sc.enabled is True
assert sc.scan_input is True
assert sc.scan_output is True
assert sc.mode == "warn"
assert sc.secret_scanner is True
assert sc.pii_scanner is True
assert sc.enforce_tool_confirmation is True
def test_security_config_on_jarvis_config(self) -> None:
cfg = JarvisConfig()
assert isinstance(cfg.security, SecurityConfig)
def test_security_config_loads_from_toml(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text('[security]\nmode = "block"\nscan_input = false\n')
cfg = load_config(toml_file)
assert cfg.security.mode == "block"
assert cfg.security.scan_input is False
def test_security_config_in_default_toml(self) -> None:
output = generate_default_toml(HardwareInfo())
assert "[security]" in output
class TestChannelConfig:
def test_channel_config_defaults(self) -> None:
cc = ChannelConfig()
assert cc.enabled is False
assert cc.default_agent == "simple"
def test_channel_config_on_jarvis_config(self) -> None:
cfg = JarvisConfig()
assert isinstance(cfg.channel, ChannelConfig)
def test_channel_config_loads_from_toml(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[channel]\nenabled = true\ndefault_channel = "telegram"\n'
)
cfg = load_config(toml_file)
assert cfg.channel.enabled is True
assert cfg.channel.default_channel == "telegram"
def test_channel_config_in_default_toml(self) -> None:
output = generate_default_toml(HardwareInfo())
assert "[channel]" in output
# ---------------------------------------------------------------------------
# New config structure tests
# ---------------------------------------------------------------------------
class TestIntelligenceGenerationDefaults:
def test_generation_fields_exist(self) -> None:
ic = IntelligenceConfig()
assert ic.temperature == 0.7
assert ic.max_tokens == 1024
assert ic.top_p == 0.9
assert ic.top_k == 40
assert ic.repetition_penalty == 1.0
assert ic.stop_sequences == ""
def test_custom_generation_values(self) -> None:
ic = IntelligenceConfig(temperature=0.3, max_tokens=512, top_p=0.5)
assert ic.temperature == 0.3
assert ic.max_tokens == 512
assert ic.top_p == 0.5
class TestAgentConfigNew:
def test_new_fields(self) -> None:
ac = AgentConfig()
assert ac.objective == ""
assert ac.system_prompt == ""
assert ac.system_prompt_path == ""
assert ac.context_from_memory is True
assert ac.tools == ""
assert ac.max_turns == 10
def test_default_tools_backward_compat(self) -> None:
ac = AgentConfig()
ac.default_tools = "calculator,think"
assert ac.tools == "calculator,think"
assert ac.default_tools == "calculator,think"
def test_no_temperature_or_max_tokens(self) -> None:
ac = AgentConfig()
assert not hasattr(ac.__class__, "temperature") or isinstance(
getattr(ac.__class__, "temperature", None), property
) is False
class TestNestedEngineConfig:
def test_nested_access(self) -> None:
ec = EngineConfig()
assert ec.ollama.host == ""
assert ec.vllm.host == "http://localhost:8000"
assert ec.sglang.host == "http://localhost:30000"
assert ec.llamacpp.host == "http://localhost:8080"
assert ec.llamacpp.binary_path == ""
def test_backward_compat_setter(self) -> None:
ec = EngineConfig()
ec.ollama_host = "http://custom:1234"
assert ec.ollama.host == "http://custom:1234"
assert ec.ollama_host == "http://custom:1234"
def test_llamacpp_path_compat(self) -> None:
ec = EngineConfig()
ec.llamacpp_path = "/usr/bin/llama-server"
assert ec.llamacpp.binary_path == "/usr/bin/llama-server"
def test_loads_nested_toml(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[engine]\ndefault = "vllm"\n\n'
'[engine.ollama]\nhost = "http://custom:11434"\n\n'
'[engine.llamacpp]\nbinary_path = "/opt/llama"\n'
)
cfg = load_config(toml_file)
assert cfg.engine.default == "vllm"
assert cfg.engine.ollama.host == "http://custom:11434"
assert cfg.engine.llamacpp.binary_path == "/opt/llama"
def test_loads_old_flat_toml(self, tmp_path: Path) -> None:
"""Old flat engine keys still work via backward-compat properties."""
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[engine]\ndefault = "ollama"\n'
'ollama_host = "http://old:11434"\n'
'vllm_host = "http://old:8000"\n'
)
cfg = load_config(toml_file)
assert cfg.engine.ollama.host == "http://old:11434"
assert cfg.engine.vllm.host == "http://old:8000"
class TestNestedLearningConfig:
def test_defaults(self) -> None:
lc = LearningConfig()
assert lc.enabled is False
assert lc.update_interval == 100
assert lc.auto_update is False
assert lc.routing.policy == "heuristic"
assert lc.routing.min_samples == 5
assert lc.intelligence.policy == "none"
assert lc.agent.policy == "none"
assert lc.metrics.accuracy_weight == 0.6
assert lc.metrics.latency_weight == 0.2
def test_backward_compat_default_policy(self) -> None:
lc = LearningConfig()
assert lc.default_policy == "heuristic"
lc.default_policy = "grpo"
assert lc.routing.policy == "grpo"
def test_backward_compat_intelligence_policy(self) -> None:
lc = LearningConfig()
lc.intelligence_policy = "sft"
assert lc.intelligence.policy == "sft"
def test_backward_compat_agent_policy(self) -> None:
lc = LearningConfig()
lc.agent_policy = "agent_advisor"
assert lc.agent.policy == "agent_advisor"
def test_backward_compat_reward_weights(self) -> None:
lc = LearningConfig()
lc.reward_weights = "latency=0.4,cost=0.3"
assert lc.metrics.latency_weight == 0.4
assert lc.metrics.cost_weight == 0.3
def test_loads_nested_toml(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[learning]\nenabled = true\nupdate_interval = 50\n\n'
'[learning.routing]\npolicy = "learned"\n\n'
'[learning.metrics]\nlatency_weight = 0.5\n'
)
cfg = load_config(toml_file)
assert cfg.learning.enabled is True
assert cfg.learning.update_interval == 50
assert cfg.learning.routing.policy == "learned"
assert cfg.learning.metrics.latency_weight == 0.5
def test_loads_old_flat_learning_toml(self, tmp_path: Path) -> None:
"""Old flat learning keys still work via backward-compat properties."""
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[learning]\ndefault_policy = "grpo"\n'
'intelligence_policy = "sft"\n'
'reward_weights = "latency=0.5"\n'
)
cfg = load_config(toml_file)
assert cfg.learning.routing.policy == "grpo"
assert cfg.learning.intelligence.policy == "sft"
assert cfg.learning.metrics.latency_weight == 0.5
class TestBackwardCompatMigration:
def test_agent_temperature_migrates_to_intelligence(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[agent]\ntemperature = 0.3\nmax_tokens = 512\n'
)
cfg = load_config(toml_file)
assert cfg.intelligence.temperature == 0.3
assert cfg.intelligence.max_tokens == 512
def test_memory_context_injection_migrates_to_agent(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text('[memory]\ncontext_injection = false\n')
cfg = load_config(toml_file)
assert cfg.agent.context_from_memory is False
def test_tools_storage_context_injection_migrates(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[tools.storage]\ncontext_injection = false\ndefault_backend = "faiss"\n'
)
cfg = load_config(toml_file)
assert cfg.agent.context_from_memory is False
assert cfg.tools.storage.default_backend == "faiss"
class TestGenerateDefaultTomlNew:
def test_nested_engine_sections(self) -> None:
hw = HardwareInfo()
toml_str = generate_default_toml(hw)
assert "[engine.ollama]" in toml_str
assert "[engine.vllm]" in toml_str
assert "[engine.sglang]" in toml_str
def test_intelligence_generation_params(self) -> None:
hw = HardwareInfo()
toml_str = generate_default_toml(hw)
assert "temperature = 0.7" in toml_str
assert "max_tokens = 1024" in toml_str
def test_agent_new_fields(self) -> None:
hw = HardwareInfo()
toml_str = generate_default_toml(hw)
assert "context_from_memory = true" in toml_str
def test_learning_nested_sections(self) -> None:
hw = HardwareInfo()
toml_str = generate_default_toml(hw)
assert "[learning.routing]" in toml_str
assert 'policy = "heuristic"' in toml_str
# ---------------------------------------------------------------------------
# Sandbox config tests
# ---------------------------------------------------------------------------
class TestSandboxConfig:
def test_defaults(self) -> None:
sc = SandboxConfig()
assert sc.enabled is False
assert sc.image == "openjarvis-sandbox:latest"
assert sc.timeout == 300
assert sc.workspace == ""
assert sc.mount_allowlist_path == ""
assert sc.max_concurrent == 5
assert sc.runtime == "docker"
def test_custom_values(self) -> None:
sc = SandboxConfig(
enabled=True,
image="custom:v2",
timeout=600,
runtime="podman",
)
assert sc.enabled is True
assert sc.image == "custom:v2"
assert sc.timeout == 600
assert sc.runtime == "podman"
def test_on_jarvis_config(self) -> None:
cfg = JarvisConfig()
assert isinstance(cfg.sandbox, SandboxConfig)
assert cfg.sandbox.enabled is False
def test_loads_from_toml(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[sandbox]\nenabled = true\ntimeout = 600\n'
'runtime = "podman"\n'
)
cfg = load_config(toml_file)
assert cfg.sandbox.enabled is True
assert cfg.sandbox.timeout == 600
assert cfg.sandbox.runtime == "podman"
# ---------------------------------------------------------------------------
# Scheduler config tests
# ---------------------------------------------------------------------------
class TestSchedulerConfig:
def test_defaults(self) -> None:
sc = SchedulerConfig()
assert sc.enabled is False
assert sc.poll_interval == 60
assert sc.db_path == ""
def test_custom_values(self) -> None:
sc = SchedulerConfig(enabled=True, poll_interval=30, db_path="/tmp/sched.db")
assert sc.enabled is True
assert sc.poll_interval == 30
assert sc.db_path == "/tmp/sched.db"
def test_on_jarvis_config(self) -> None:
cfg = JarvisConfig()
assert isinstance(cfg.scheduler, SchedulerConfig)
assert cfg.scheduler.enabled is False
def test_loads_from_toml(self, tmp_path: Path) -> None:
toml_file = tmp_path / "config.toml"
toml_file.write_text(
'[scheduler]\nenabled = true\npoll_interval = 30\n'
'db_path = "/tmp/sched.db"\n'
)
cfg = load_config(toml_file)
assert cfg.scheduler.enabled is True
assert cfg.scheduler.poll_interval == 30
assert cfg.scheduler.db_path == "/tmp/sched.db"
# ---------------------------------------------------------------------------
# WhatsApp Baileys channel config tests
# ---------------------------------------------------------------------------
class TestWhatsAppBaileysChannelConfig:
def test_defaults(self) -> None:
wc = WhatsAppBaileysChannelConfig()
assert wc.auth_dir == ""
assert wc.assistant_name == "Jarvis"
assert wc.assistant_has_own_number is False
def test_custom_values(self) -> None:
wc = WhatsAppBaileysChannelConfig(
auth_dir="/tmp/wa",
assistant_name="Andy",
assistant_has_own_number=True,
)
assert wc.auth_dir == "/tmp/wa"
assert wc.assistant_name == "Andy"
assert wc.assistant_has_own_number is True
def test_on_channel_config(self) -> None:
cc = ChannelConfig()
assert isinstance(cc.whatsapp_baileys, WhatsAppBaileysChannelConfig)