Files
OpenJarvis/tests/prompt/test_builder.py
T
b3cfa398b4 fix(config): honor system_prompt.prefix from config.toml (#482)
A [system_prompt] prefix set in config.toml was silently ignored: (1)
load_config()'s section allowlist dropped the [system_prompt],
[memory_files], [compression], and [skills] blocks entirely; (2)
SystemPromptConfig had no prefix field; (3) the builder never prepended
one.

Add the four blocks to the allowlist, add prefix: str = "" to
SystemPromptConfig, and prepend a "prefix" PromptSection at the front of
SystemPromptBuilder's frozen sections so it leads build() output and is
exposed via sections() (#457). Empty prefix emits no section — existing
configs are byte-for-byte unchanged.

Rebuilt on top of #457 (which refactored the builder to PromptSection
objects); the original #452 by @SoulSniper-V2 patched the pre-#457
method. Credit to @SoulSniper-V2. Adds the regression tests the original
PR lacked: config parse + prefix-prepended + empty-prefix-unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 20:14:42 -07:00

224 lines
7.8 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
from openjarvis.core.config import MemoryFilesConfig, SystemPromptConfig
@pytest.fixture
def memory_dir(tmp_path: Path) -> Path:
soul = tmp_path / "SOUL.md"
soul.write_text("You are a helpful research assistant.")
memory = tmp_path / "MEMORY.md"
memory.write_text("- User prefers concise answers\n- User is a data scientist")
user = tmp_path / "USER.md"
user.write_text("- Name: Alice\n- Role: ML Engineer")
return tmp_path
def test_build_frozen_prefix(memory_dir: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(),
)
prompt = builder.build()
assert "Jarvis" in prompt
assert "helpful research assistant" in prompt
assert "concise answers" in prompt
assert "Alice" in prompt
def test_config_prefix_prepended(memory_dir: Path):
"""Regression for #401: a configured system_prompt.prefix leads the
assembled prompt, ahead of the agent template, and is exposed as a
'prefix' section."""
from openjarvis.prompt.builder import SystemPromptBuilder
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(prefix="ALWAYS ANSWER AS JARVIS."),
)
prompt = builder.build()
assert prompt.startswith("ALWAYS ANSWER AS JARVIS.")
assert "You are Jarvis." in prompt
# Prefix is visible in the inspection API too (#457), as a frozen section.
section_names = [s.name for s in builder.sections()]
assert section_names[0] == "prefix"
assert builder.sections()[0].cache_segment == "frozen_prefix"
def test_empty_prefix_leaves_prompt_unchanged(memory_dir: Path):
"""Backward compatibility: the default empty prefix adds no section and
leaves build() output identical to having no prefix configured."""
from openjarvis.prompt.builder import SystemPromptBuilder
def _make(prefix: str) -> SystemPromptBuilder:
return SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(prefix=prefix),
)
assert _make("").build() == _make("").build()
# No 'prefix' section is emitted when prefix is empty.
assert "prefix" not in [s.name for s in _make("").sections()]
# And the first section is the agent template, as before.
assert _make("").sections()[0].name == "agent_template"
def test_frozen_prefix_stability(memory_dir: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(),
)
first = builder.build()
(memory_dir / "MEMORY.md").write_text("- CHANGED CONTENT")
second = builder.build()
assert first == second
def test_char_limit_truncation(memory_dir: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
(memory_dir / "SOUL.md").write_text("x" * 10000)
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(soul_max_chars=100),
)
prompt = builder.build()
assert prompt.count("x") <= 100
assert "truncated" in prompt.lower()
def test_skill_index_in_prompt(memory_dir: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
skills = [("api_health_check", "Check API health across all endpoints")]
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(),
skill_index=skills,
)
prompt = builder.build()
assert "api_health_check" in prompt
assert "Check API health" in prompt
def test_dynamic_section_appended(memory_dir: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(),
session_context="Platform: CLI | Session: abc123",
)
prompt = builder.build()
assert "Platform: CLI" in prompt
def test_sections_expose_prompt_metadata(memory_dir: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(),
session_context="Platform: CLI | Session: abc123",
previous_state="Last task: summarize telemetry.",
)
sections = builder.sections()
assert [section.name for section in sections] == [
"agent_template",
"soul",
"memory",
"user",
"session_context",
"previous_state",
]
assert sections[1].source == str(memory_dir / "SOUL.md")
assert sections[1].cache_segment == "frozen_prefix"
assert sections[-1].cache_segment == "dynamic_suffix"
assert builder.build() == "\n\n".join(section.content for section in sections)
def test_sections_keep_frozen_file_content_stable(memory_dir: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(memory_dir / "SOUL.md"),
memory_path=str(memory_dir / "MEMORY.md"),
user_path=str(memory_dir / "USER.md"),
),
system_prompt_config=SystemPromptConfig(),
)
first = builder.sections()
(memory_dir / "MEMORY.md").write_text("- CHANGED CONTENT")
second = builder.sections()
assert first == second
def test_missing_files_handled(tmp_path: Path):
from openjarvis.prompt.builder import SystemPromptBuilder
builder = SystemPromptBuilder(
agent_template="You are Jarvis.",
memory_files_config=MemoryFilesConfig(
soul_path=str(tmp_path / "missing_soul.md"),
memory_path=str(tmp_path / "missing_memory.md"),
user_path=str(tmp_path / "missing_user.md"),
),
system_prompt_config=SystemPromptConfig(),
)
prompt = builder.build()
assert "Jarvis" in prompt