Spaces:
Sleeping
Sleeping
| import pytest | |
| from app.schemas.commit_adaptation import CommitAdaptationReview, InteractionQuote, MemoryProposal | |
| from app.services import ( | |
| approved_profile_staging, | |
| commit_adaptation, | |
| pending_review_store, | |
| student_interaction_log, | |
| ) | |
| class _FakeReviewer: | |
| def __init__(self, review: CommitAdaptationReview): | |
| self._review = review | |
| self.calls = 0 | |
| async def review_commit_with_llm(self, **kwargs): | |
| self.calls += 1 | |
| return self._review | |
| class _FakeProjectMemory: | |
| def __init__(self): | |
| self.written_records = [] | |
| def upsert(self, records): | |
| self.written_records.extend(records) | |
| return len(records) | |
| class _FakeStudentMemory: | |
| def __init__(self, flush_ok: bool = True, stage_ok: bool = True): | |
| self.flush_ok = flush_ok | |
| self.stage_ok = stage_ok | |
| self.staged_candidates = [] | |
| async def query_prior_knowledge(self, *args, **kwargs): | |
| return "No prior profile." | |
| async def stage_promoted_candidate(self, candidate): | |
| self.staged_candidates.append(candidate) | |
| return self.stage_ok | |
| async def flush_project(self, project_id): | |
| return {"research_profile": self.flush_ok} | |
| class _FakeAgentControlPersona: | |
| def __init__(self): | |
| self.persona_calls = [] | |
| def get_inferred_text(self, project_id): | |
| return "" | |
| def get_manual_text(self, project_id): | |
| return "" | |
| def set_inferred_text(self, project_id, text): | |
| self.persona_calls.append((project_id, text)) | |
| def _isolate_paths(tmp_path, monkeypatch): | |
| monkeypatch.setattr(student_interaction_log, "_ROOT", tmp_path / "student_interactions") | |
| monkeypatch.setattr(approved_profile_staging, "_ROOT", tmp_path / "approved_profile_staging") | |
| monkeypatch.setattr(pending_review_store, "_ROOT", tmp_path / "pending_reviews") | |
| monkeypatch.setattr(pending_review_store, "_UNCERTAIN_ROOT", tmp_path / "uncertain_flushes") | |
| async def test_run_commit_adaptation_is_noop_with_no_pending_interactions(monkeypatch): | |
| reviewer = _FakeReviewer(CommitAdaptationReview(memory_proposals=[], inferred_persona_text="")) | |
| monkeypatch.setattr(commit_adaptation, "agent_control", reviewer) | |
| await commit_adaptation.run_commit_adaptation(project_id="project-a") | |
| assert reviewer.calls == 0 | |
| async def test_run_commit_adaptation_persists_project_candidate_to_chroma(monkeypatch): | |
| interaction = student_interaction_log.append_student_interaction( | |
| project_id="project-a", source="chat_turn", agent_id="tutor", text="I use PyTorch for everything.", | |
| ) | |
| review = CommitAdaptationReview( | |
| memory_proposals=[ | |
| MemoryProposal( | |
| proposal_id="p1", destination="project", kind="project_observation", | |
| statement="Uses PyTorch for all experiments.", | |
| interaction_ids=[interaction.interaction_id], | |
| evidence_quotes=[ | |
| InteractionQuote(interaction_id=interaction.interaction_id, quote="I use PyTorch for everything.") | |
| ], | |
| confidence=0.8, | |
| ) | |
| ], | |
| inferred_persona_text="Assume PyTorch familiarity.", | |
| ) | |
| reviewer = _FakeReviewer(review) | |
| monkeypatch.setattr(commit_adaptation, "agent_control", reviewer) | |
| fake_project_memory = _FakeProjectMemory() | |
| monkeypatch.setattr(commit_adaptation, "project_memory", fake_project_memory) | |
| fake_student_memory = _FakeStudentMemory() | |
| monkeypatch.setattr(commit_adaptation, "student_memory", fake_student_memory) | |
| fake_persona = _FakeAgentControlPersona() | |
| monkeypatch.setattr(commit_adaptation, "agent_control_persona", fake_persona) | |
| await commit_adaptation.run_commit_adaptation(project_id="project-a") | |
| assert len(fake_project_memory.written_records) == 1 | |
| assert fake_project_memory.written_records[0].statement == "Uses PyTorch for all experiments." | |
| assert fake_persona.persona_calls == [("project-a", "Assume PyTorch familiarity.")] | |
| assert pending_review_store.load_pending_review("project-a") is None | |
| async def test_cognee_failure_marks_uncertain_and_does_not_update_persona(monkeypatch): | |
| interaction = student_interaction_log.append_student_interaction( | |
| project_id="project-a", source="chat_turn", agent_id="tutor", text="I generally prefer diagrams.", | |
| ) | |
| review = CommitAdaptationReview( | |
| memory_proposals=[ | |
| MemoryProposal( | |
| proposal_id="p1", destination="student", kind="learning_style", | |
| statement="Generally prefers diagrams.", | |
| interaction_ids=[interaction.interaction_id], | |
| evidence_quotes=[ | |
| InteractionQuote(interaction_id=interaction.interaction_id, quote="I generally prefer diagrams.") | |
| ], | |
| confidence=0.9, | |
| ) | |
| ], | |
| inferred_persona_text="Lean on diagrams.", | |
| ) | |
| reviewer = _FakeReviewer(review) | |
| monkeypatch.setattr(commit_adaptation, "agent_control", reviewer) | |
| monkeypatch.setattr(commit_adaptation, "project_memory", _FakeProjectMemory()) | |
| fake_student_memory = _FakeStudentMemory(flush_ok=False) | |
| monkeypatch.setattr(commit_adaptation, "student_memory", fake_student_memory) | |
| fake_persona = _FakeAgentControlPersona() | |
| monkeypatch.setattr(commit_adaptation, "agent_control_persona", fake_persona) | |
| await commit_adaptation.run_commit_adaptation(project_id="project-a") | |
| # "I generally prefer diagrams." matches the explicit-global signal | |
| # patterns, so the gate promotes it immediately (bypassing recurrence) -- | |
| # this is what actually reaches the flush-attempt code path. | |
| assert fake_student_memory.staged_candidates, "candidate was never promoted -- test doesn't exercise a real flush failure" | |
| assert fake_persona.persona_calls == [] | |
| pending = pending_review_store.load_pending_review("project-a") | |
| assert pending is not None | |
| assert pending.status == "cognee_flush_uncertain" | |
| async def test_stale_flush_started_batch_is_archived_and_never_replayed(monkeypatch): | |
| student_interaction_log.append_student_interaction( | |
| project_id="project-a", source="chat_turn", agent_id="tutor", text="Some message.", | |
| ) | |
| interactions, i_from, i_through = student_interaction_log.read_pending("project-a") | |
| pending_review_store.save_pending_review( | |
| "project-a", status="cognee_flush_started", candidate_ids=["c1"], | |
| interaction_from_offset=i_from, interaction_through_offset=i_through, | |
| profile_staging_from_offset=0, profile_staging_through_offset=0, | |
| review=CommitAdaptationReview(memory_proposals=[], inferred_persona_text="stale"), | |
| flush_attempt_id="crashed-run", | |
| ) | |
| reviewer = _FakeReviewer(CommitAdaptationReview(memory_proposals=[], inferred_persona_text="")) | |
| monkeypatch.setattr(commit_adaptation, "agent_control", reviewer) | |
| await commit_adaptation.run_commit_adaptation(project_id="project-a") | |
| assert reviewer.calls == 0 # archived-and-recovered, must not fall through into a fresh review this same call | |
| assert pending_review_store.load_pending_review("project-a") is None | |
| interactions_after, _, _ = student_interaction_log.read_pending("project-a") | |
| assert interactions_after == [] # cursor advanced past the archived batch | |
| async def test_ready_to_flush_retry_reuses_exact_stored_ranges_not_expanded_ones(monkeypatch): | |
| interaction = student_interaction_log.append_student_interaction( | |
| project_id="project-a", source="chat_turn", agent_id="tutor", text="I use PyTorch for everything.", | |
| ) | |
| _, i_from, i_through = student_interaction_log.read_pending("project-a") | |
| stored_review = CommitAdaptationReview( | |
| memory_proposals=[ | |
| MemoryProposal( | |
| proposal_id="p1", destination="project", kind="project_observation", | |
| statement="Uses PyTorch for all experiments.", | |
| interaction_ids=[interaction.interaction_id], | |
| evidence_quotes=[ | |
| InteractionQuote(interaction_id=interaction.interaction_id, quote="I use PyTorch for everything.") | |
| ], | |
| confidence=0.8, | |
| ) | |
| ], | |
| inferred_persona_text="Assume PyTorch familiarity.", | |
| ) | |
| from app.services.commit_candidate_adapters import proposal_to_memory_candidate | |
| candidate = proposal_to_memory_candidate( | |
| stored_review.memory_proposals[0], project_id="project-a", | |
| interactions_by_id={interaction.interaction_id: interaction}, | |
| ) | |
| pending_review_store.save_pending_review( | |
| "project-a", status="ready_to_flush", candidate_ids=[candidate.candidate_id], | |
| interaction_from_offset=i_from, interaction_through_offset=i_through, | |
| profile_staging_from_offset=0, profile_staging_through_offset=0, | |
| review=stored_review, | |
| ) | |
| # A new interaction arrives after the pending review was captured -- | |
| # it must NOT be swept into this retry's batch. | |
| student_interaction_log.append_student_interaction( | |
| project_id="project-a", source="chat_turn", agent_id="tutor", text="Unrelated later message.", | |
| ) | |
| reviewer = _FakeReviewer(CommitAdaptationReview(memory_proposals=[], inferred_persona_text="should not be called")) | |
| monkeypatch.setattr(commit_adaptation, "agent_control", reviewer) | |
| fake_project_memory = _FakeProjectMemory() | |
| monkeypatch.setattr(commit_adaptation, "project_memory", fake_project_memory) | |
| fake_student_memory = _FakeStudentMemory() | |
| monkeypatch.setattr(commit_adaptation, "student_memory", fake_student_memory) | |
| fake_persona = _FakeAgentControlPersona() | |
| monkeypatch.setattr(commit_adaptation, "agent_control_persona", fake_persona) | |
| await commit_adaptation.run_commit_adaptation(project_id="project-a") | |
| assert reviewer.calls == 0, "retry must not call the LLM again" | |
| assert len(fake_project_memory.written_records) == 1 | |
| assert fake_project_memory.written_records[0].statement == "Uses PyTorch for all experiments." | |
| assert fake_persona.persona_calls == [("project-a", "Assume PyTorch familiarity.")] | |
| # The cursor only advanced to the ORIGINAL through_offset -- the later, | |
| # unrelated message remains pending for the next Commit. | |
| remaining, _, _ = student_interaction_log.read_pending("project-a") | |
| assert [i.text for i in remaining] == ["Unrelated later message."] | |