mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 14:07:55 +00:00
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>
224 lines
7.8 KiB
Python
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
|