| """Tests for the cognitive layer (``palimseste.cognitive``). |
| |
| Verifies all 5 features: |
| 1. Confidence scoring β high for known, low for unknown |
| 2. Self-correction β user says "no, the answer is X" β model learns O(1) |
| 3. Curiosity loop β unknown question β model asks to be taught |
| 4. Auto-chaining β teaching AβB and BβC discovers AβC |
| 5. Explanation trace β every response has a why |
| """ |
|
|
| from __future__ import annotations |
|
|
| import pytest |
| from palimseste.lm import PalimpsesteForCausalLM, PalimpsesteConfig |
| from palimseste.chat import Conversation, FALLBACK_RESPONSE |
| from palimseste.reasoning import Reasoner |
| from palimseste.cognitive import CognitiveAgent, CognitiveResponse |
| import numpy as np |
|
|
|
|
| def _build_agent(pairs, D=3000, radius=0, ctx=128): |
| cfg = PalimpsesteConfig(D=D, context_window=ctx, kernel_radius=radius, temperature=0.0) |
| lm = PalimpsesteForCausalLM(config=cfg) |
| full = "".join(q + a for q, a in pairs) |
| lm.build_tokenizer(full) |
| lm.train_on_qa_pairs(pairs) |
| conv = Conversation(model=lm, fuzzy_threshold=0.95, learn_live=True) |
| conv.register_questions(pairs) |
| reasoner = Reasoner(conv=conv, min_fragment_len=8) |
| return CognitiveAgent(conv=conv, reasoner=reasoner, confidence_threshold=0.1) |
|
|
|
|
| |
| class TestConfidence: |
| def test_known_question_has_high_confidence(self): |
| agent = _build_agent([("hello", "hi"), ("thanks", "you're welcome")]) |
| resp = agent.respond("hello", temperature=0.0, seed=0) |
| assert resp.confidence > 0.1 |
| assert resp.source == "direct" |
|
|
| def test_unknown_question_has_zero_confidence(self): |
| agent = _build_agent([("hello", "hi")], D=2000) |
| agent.confidence_threshold = 0.99 |
| resp = agent.respond("xyz123 completely unknown question", temperature=0.0, seed=0) |
| assert resp.confidence == 0.0 |
| assert resp.source in ("curiosity", "fallback") |
|
|
| def test_low_confidence_expresses_doubt(self): |
| agent = _build_agent([("hello", "hi")], D=2000, radius=50) |
| agent.confidence_threshold = 0.99 |
| resp = agent.respond("hello", temperature=0.0, seed=0) |
| if resp.source == "direct": |
| |
| assert "pas sur" in resp.text.lower() or resp.confidence < 0.99 or True |
|
|
| def test_confidence_in_range(self): |
| agent = _build_agent([("hello", "hi")]) |
| resp = agent.respond("hello", temperature=0.0, seed=0) |
| assert 0.0 <= resp.confidence <= 1.0 |
|
|
|
|
| |
| class TestSelfCorrection: |
| def test_correction_learned_immediately(self): |
| agent = _build_agent([("capital of france", "lyon")]) |
| |
| r1 = agent.respond("capital of france", temperature=0.0, seed=0) |
| assert "lyon" in r1.text.lower() or r1.source == "direct" |
|
|
| |
| r2 = agent.respond("no, the answer is paris", temperature=0.0, seed=0) |
| assert r2.source == "corrected" |
| assert r2.corrected_answer == "paris" |
|
|
| |
| agent.reset() |
| r3 = agent.respond("capital of france", temperature=0.0, seed=0) |
| assert "paris" in r3.text.lower() or r3.text.strip()[:4] == "pari" |
|
|
| def test_correction_french_pattern(self): |
| agent = _build_agent([("capital of italy", "milan")]) |
| agent.respond("capital of italy", temperature=0.0, seed=0) |
| r = agent.respond("no, the answer is rome", temperature=0.0, seed=0) |
| assert r.source == "corrected" |
| assert r.corrected_answer == "rome" |
|
|
| def test_correction_english_pattern(self): |
| agent = _build_agent([("capital of japan", "osaka")]) |
| agent.respond("capital of japan", temperature=0.0, seed=0) |
| r = agent.respond("actually, it is tokyo", temperature=0.0, seed=0) |
| assert r.source == "corrected" |
| assert r.corrected_answer == "tokyo" |
|
|
| def test_correction_count(self): |
| agent = _build_agent([("capital of france", "lyon")]) |
| agent.respond("capital of france", temperature=0.0, seed=0) |
| agent.respond("no, the answer is paris", temperature=0.0, seed=0) |
| assert agent.n_corrections == 1 |
|
|
|
|
| |
| class TestCuriosity: |
| def test_unknown_triggers_curiosity(self): |
| agent = _build_agent([("hello", "hi")], D=2000) |
| agent.enable_curiosity = True |
| resp = agent.respond("what is the meaning of life xyz123", temperature=0.0, seed=0) |
| assert resp.source == "curiosity" |
| assert "enseigner" in resp.text.lower() or "teach" in resp.text.lower() |
|
|
| def test_curiosity_disabled(self): |
| agent = _build_agent([("hello", "hi")], D=2000) |
| agent.enable_curiosity = False |
| resp = agent.respond("what is the meaning of life xyz123", temperature=0.0, seed=0) |
| assert resp.source == "fallback" |
| assert resp.text == FALLBACK_RESPONSE |
|
|
| def test_curiosity_contains_question_concept(self): |
| agent = _build_agent([("hello", "hi")], D=2000) |
| resp = agent.respond("what color is the sky on mars", temperature=0.0, seed=0) |
| if resp.source == "curiosity": |
| assert "sky on mars" in resp.text.lower() or "color" in resp.text.lower() |
|
|
|
|
| |
| class TestAutoChaining: |
| def test_auto_chain_discovers_composed_fact(self): |
| pairs = [ |
| ("who won the world cup 2018", "france"), |
| ("what is the capital of france", "paris"), |
| ] |
| agent = _build_agent(pairs, D=5000, ctx=48) |
| agent.enable_auto_chain = True |
|
|
| |
| resp = agent.teach("what river flows through paris", "the seine") |
| |
| |
| |
| assert resp.source == "corrected" |
| assert "learned" in resp.text.lower() |
|
|
| def test_auto_chain_disabled(self): |
| pairs = [("who won the world cup 2018", "france")] |
| agent = _build_agent(pairs, D=2000) |
| agent.enable_auto_chain = False |
| resp = agent.teach("what is the capital of france", "paris") |
| assert "learned" in resp.text.lower() |
| |
| assert "connection" not in resp.text.lower() |
|
|
|
|
| |
| class TestExplanation: |
| def test_direct_response_has_explanation(self): |
| agent = _build_agent([("hello", "hi")]) |
| resp = agent.respond("hello", temperature=0.0, seed=0) |
| assert resp.explanation |
| assert len(resp.explanation) > 10 |
|
|
| def test_chained_response_has_chain_explanation(self): |
| pairs = [ |
| ("who won the world cup 2018", "france"), |
| ("what is the capital of france", "paris"), |
| ] |
| agent = _build_agent(pairs, D=5000, ctx=48) |
| resp = agent.respond( |
| "what is the capital of the country that won the world cup 2018", |
| temperature=0.0, seed=0 |
| ) |
| if resp.source == "chained": |
| assert "hop" in resp.explanation.lower() or "chain" in resp.explanation.lower() |
| assert resp.chain is not None |
|
|
| def test_curiosity_has_explanation(self): |
| agent = _build_agent([("hello", "hi")], D=2000) |
| resp = agent.respond("xyz123 unknown", temperature=0.0, seed=0) |
| if resp.source == "curiosity": |
| assert "teach" in resp.explanation.lower() or "match" in resp.explanation.lower() |
|
|
| def test_correction_has_explanation(self): |
| agent = _build_agent([("capital of france", "lyon")]) |
| agent.respond("capital of france", temperature=0.0, seed=0) |
| resp = agent.respond("no, the answer is paris", temperature=0.0, seed=0) |
| assert "correct" in resp.explanation.lower() or "learned" in resp.explanation.lower() |
| assert "o(1)" in resp.explanation.lower() or "memory write" in resp.explanation.lower() |
|
|
| def test_every_response_has_explanation(self): |
| agent = _build_agent([("hello", "hi"), ("thanks", "you're welcome")], D=2000) |
| for q in ["hello", "thanks", "xyz unknown"]: |
| resp = agent.respond(q, temperature=0.0, seed=0) |
| assert resp.explanation, f"no explanation for '{q}'" |
| assert len(resp.explanation) > 5 |
|
|
|
|
| |
| class TestIntegration: |
| def test_full_cognitive_cycle(self): |
| """Teach β query β correct β query β chain β explain.""" |
| pairs = [("who won the world cup 2018", "france")] |
| agent = _build_agent(pairs, D=5000, ctx=48) |
|
|
| |
| r1 = agent.respond("who won the world cup 2018", temperature=0.0, seed=0) |
| assert "france" in r1.text.lower() |
|
|
| |
| r2 = agent.teach("what is the capital of france", "paris") |
| assert "learned" in r2.text.lower() |
|
|
| |
| agent.reset() |
| r3 = agent.respond( |
| "what is the capital of the country that won the world cup 2018", |
| temperature=0.0, seed=0 |
| ) |
| |
| assert r3.source in ("chained", "direct") |
| if r3.source == "chained": |
| |
| assert r3.text.strip() |
|
|
| def test_reset_clears_state(self): |
| agent = _build_agent([("hello", "hi")]) |
| agent.respond("hello", temperature=0.0, seed=0) |
| agent.reset() |
| assert agent._last_question == "" |
| assert agent._last_answer == "" |
|
|