Some checks failed
Contributor Attribution Check / check-attribution (pull_request) Successful in 29s
Docker Build and Publish / build-and-push (pull_request) Has been skipped
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 33s
Tests / e2e (pull_request) Successful in 3m26s
Tests / test (pull_request) Failing after 1h28m50s
Before compressing conversation context, extract durable facts (user preferences, corrections, project details) and save to fact store so they survive compression. New agent/session_compactor.py: - extract_facts_from_messages(): scans user messages for preferences, corrections, project/infra facts using regex - 3 pattern categories: user_pref (5 patterns), correction (3 patterns), project (4 patterns) - ExtractedFact: category, entity, content, confidence, source_turn - save_facts_to_store(): saves to fact store (callback or auto-detect) - extract_and_save_facts(): one-call extraction + persistence - Deduplication by category+content - Skips tool results, short messages, system messages - format_facts_summary(): human-readable summary Tests: tests/test_session_compactor.py (9 tests) Closes #748
92 lines
2.9 KiB
Python
92 lines
2.9 KiB
Python
"""Tests for session compaction with fact extraction."""
|
|
|
|
import pytest
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from agent.session_compactor import (
|
|
ExtractedFact,
|
|
extract_facts_from_messages,
|
|
save_facts_to_store,
|
|
extract_and_save_facts,
|
|
format_facts_summary,
|
|
)
|
|
|
|
|
|
class TestFactExtraction:
|
|
def test_extract_preference(self):
|
|
messages = [
|
|
{"role": "user", "content": "I prefer Python over JavaScript for backend work."},
|
|
]
|
|
facts = extract_facts_from_messages(messages)
|
|
assert len(facts) >= 1
|
|
assert any("Python" in f.content for f in facts)
|
|
|
|
def test_extract_correction(self):
|
|
messages = [
|
|
{"role": "user", "content": "Actually the port is 8081 not 8080."},
|
|
]
|
|
facts = extract_facts_from_messages(messages)
|
|
assert len(facts) >= 1
|
|
assert any("8081" in f.content for f in facts)
|
|
|
|
def test_extract_project_fact(self):
|
|
messages = [
|
|
{"role": "user", "content": "The project uses Gitea for source control."},
|
|
]
|
|
facts = extract_facts_from_messages(messages)
|
|
assert len(facts) >= 1
|
|
|
|
def test_skip_tool_results(self):
|
|
messages = [
|
|
{"role": "assistant", "content": "Running command...", "tool_calls": [{"id": "1"}]},
|
|
{"role": "tool", "content": "output here"},
|
|
]
|
|
facts = extract_facts_from_messages(messages)
|
|
assert len(facts) == 0
|
|
|
|
def test_skip_short_messages(self):
|
|
messages = [
|
|
{"role": "user", "content": "ok"},
|
|
]
|
|
facts = extract_facts_from_messages(messages)
|
|
assert len(facts) == 0
|
|
|
|
def test_deduplication(self):
|
|
messages = [
|
|
{"role": "user", "content": "I prefer Python."},
|
|
{"role": "user", "content": "I prefer Python."},
|
|
]
|
|
facts = extract_facts_from_messages(messages)
|
|
# Should deduplicate
|
|
python_facts = [f for f in facts if "Python" in f.content]
|
|
assert len(python_facts) == 1
|
|
|
|
|
|
class TestSaveFacts:
|
|
def test_save_with_callback(self):
|
|
saved = []
|
|
def mock_save(category, entity, content, trust):
|
|
saved.append({"category": category, "content": content})
|
|
|
|
facts = [ExtractedFact("user_pref", "user", "likes dark mode", 0.8, 0)]
|
|
count = save_facts_to_store(facts, fact_store_fn=mock_save)
|
|
assert count == 1
|
|
assert len(saved) == 1
|
|
|
|
|
|
class TestFormatSummary:
|
|
def test_empty(self):
|
|
assert "No facts" in format_facts_summary([])
|
|
|
|
def test_with_facts(self):
|
|
facts = [
|
|
ExtractedFact("user_pref", "user", "likes dark mode", 0.8, 0),
|
|
ExtractedFact("correction", "user", "port is 8081", 0.9, 1),
|
|
]
|
|
summary = format_facts_summary(facts)
|
|
assert "2 facts" in summary
|
|
assert "user_pref" in summary
|