Spaces:
Sleeping
Sleeping
| import pytest | |
| from app.services.citation_graph import CitationGraph | |
| from app.services.citation_topology import ResolvedSeed | |
| from app.services.scholar_service import AuthorCandidate, AuthorResolution | |
| from app.services.recommendation_service import RecommendationService | |
| from app.services.verified_paper_discovery import VerifiedDiscoveryResult | |
| QUERY = ( | |
| "i want the paper Revisiting reinforce style optimization for learning from human " | |
| "feedback in llms. also who is its authors?" | |
| ) | |
| class FakeClient: | |
| """Stands in for CerebrasClient -> returns a bare, unopinionated plan. | |
| The deterministic contract (_apply_deterministic_contract) is what's under test, and it | |
| runs *after* this LLM-shaped plan, so a minimal stub is enough here. | |
| """ | |
| def structured_complete(self, messages, output_model, **kwargs): | |
| return output_model() | |
| def _empty_graph() -> CitationGraph: | |
| return CitationGraph(project_id="project-1") | |
| def test_single_paper_seed_extracted_from_natural_phrasing(): | |
| seed = RecommendationService._single_paper_seed(QUERY) | |
| assert seed == "Revisiting reinforce style optimization for learning from human feedback in llms" | |
| def test_single_paper_seed_ignores_plural_paper_requests(): | |
| assert RecommendationService._single_paper_seed("recommend me some papers about attention") is None | |
| assert RecommendationService._single_paper_seed("find papers on contrastive learning") is None | |
| def test_context_paper_seed_resolves_this_paper_to_the_selection(): | |
| selection = "Naturalreasoning: Reasoning in the wild with 2.8m challenging questions" | |
| assert RecommendationService._context_paper_seed("/paper need this paper", selection) == selection | |
| assert RecommendationService._context_paper_seed("need this paper", selection) == selection | |
| assert RecommendationService._context_paper_seed("i want that paper", selection) == selection | |
| def test_context_paper_seed_requires_both_reference_and_selection(): | |
| assert RecommendationService._context_paper_seed("need this paper", None) is None | |
| assert RecommendationService._context_paper_seed("recommend some papers on attention", "some selection") is None | |
| async def test_recommend_resolves_exactly_one_named_paper(tmp_path): | |
| resolved = { | |
| "title": "Revisiting REINFORCE-Style Optimization for Learning from Human Feedback in LLMs", | |
| "authors": "Jane Doe, John Smith", | |
| "year": 2025, | |
| "cited_by": 3, | |
| "url": "https://example.com/reinforce-revisited", | |
| "abstract": "We revisit REINFORCE-style optimization...", | |
| "doi": "", | |
| "arxiv_id": "2501.00000", | |
| "openalex_id": "W123", | |
| "provider": "openalex", | |
| "can_acquire": True, | |
| } | |
| async def fake_title_resolver(title: str): | |
| assert "reinforce" in title.casefold() | |
| return dict(resolved) | |
| async def fake_fetcher(*args, **kwargs): | |
| raise AssertionError("generic discovery search should not run for a resolved single-paper request") | |
| service = RecommendationService( | |
| fetcher=fake_fetcher, | |
| title_resolver=fake_title_resolver, | |
| client=FakeClient(), | |
| root=str(tmp_path), | |
| ) | |
| papers, plan = await service.recommend( | |
| "project-1", | |
| QUERY, | |
| _empty_graph(), | |
| [], | |
| use_project_context=False, | |
| ) | |
| assert plan.result_limit == 1 | |
| assert len(papers) == 1 | |
| assert papers[0]["title"] == resolved["title"] | |
| assert papers[0]["authors"] == resolved["authors"] | |
| assert papers[0]["why_recommended"].startswith("The exact paper you asked for") | |
| async def test_recommend_falls_back_to_search_when_paper_unresolved(tmp_path): | |
| async def fake_title_resolver(title: str): | |
| return None | |
| async def fake_fetcher(query: str, n: int = 5, year_start=None, year_end=None): | |
| return [{ | |
| "title": "Some Closest Match", | |
| "authors": "Someone", | |
| "year": 2024, | |
| "cited_by": 1, | |
| "url": "https://example.com/closest", | |
| "abstract": "", | |
| }] | |
| service = RecommendationService( | |
| fetcher=fake_fetcher, | |
| title_resolver=fake_title_resolver, | |
| client=FakeClient(), | |
| root=str(tmp_path), | |
| ) | |
| papers, plan = await service.recommend( | |
| "project-1", | |
| QUERY, | |
| _empty_graph(), | |
| [], | |
| use_project_context=False, | |
| ) | |
| assert plan.single_paper_lookup is False | |
| assert any("Could not confidently match" in assumption for assumption in plan.assumptions) | |
| assert len(papers) >= 1 | |
| async def test_recommend_uses_selection_context_for_this_paper_requests(tmp_path): | |
| """Reproduces the reported bug: highlighting a paper title then typing "need this paper" | |
| must resolve to that highlighted paper, not generic project-graph topics.""" | |
| selection = "Naturalreasoning: Reasoning in the wild with 2.8m challenging questions" | |
| resolved = { | |
| "title": "NaturalReasoning: Reasoning in the Wild with 2.8 Million Challenging Questions", | |
| "authors": "Weizhe Yuan, Jane Doe", | |
| "year": 2025, | |
| "cited_by": 7, | |
| "url": "https://example.com/naturalreasoning", | |
| "abstract": "", | |
| } | |
| async def fake_title_resolver(title: str): | |
| assert title == selection | |
| return dict(resolved) | |
| async def fake_fetcher(*args, **kwargs): | |
| raise AssertionError("generic discovery search should not run once selection context resolves the paper") | |
| service = RecommendationService( | |
| fetcher=fake_fetcher, | |
| title_resolver=fake_title_resolver, | |
| client=FakeClient(), | |
| root=str(tmp_path), | |
| ) | |
| papers, plan = await service.recommend( | |
| "project-1", | |
| "need this paper", | |
| _empty_graph(), | |
| ["Reinforcement Learning with Verifiable Rewards and Retentive Networks"], | |
| use_project_context=False, | |
| selection_text=selection, | |
| ) | |
| assert plan.result_limit == 1 | |
| assert len(papers) == 1 | |
| assert papers[0]["title"] == resolved["title"] | |
| async def test_latest_author_query_returns_only_latest_verified_work(tmp_path): | |
| calls = [] | |
| async def fake_author_resolver(name: str): | |
| assert name == "Jonas Gehring" | |
| return AuthorResolution( | |
| status="resolved", | |
| candidates=[AuthorCandidate(openalex_id="A1", name=name)], | |
| ) | |
| async def fake_author_fetcher(author_id: str, **kwargs): | |
| calls.append((author_id, kwargs)) | |
| return [{ | |
| "title": "Newest verified work", | |
| "authors": "Jonas Gehring", | |
| "year": 2026, | |
| "cited_by": 4, | |
| "url": "https://example.com/latest", | |
| "abstract": "", | |
| "openalex_id": "W1", | |
| }] | |
| service = RecommendationService( | |
| author_resolver=fake_author_resolver, | |
| author_fetcher=fake_author_fetcher, | |
| client=FakeClient(), | |
| root=str(tmp_path), | |
| ) | |
| outcome = await service.recommend_or_clarify( | |
| project_id="project-1", | |
| query="I need the latest paper of Jonas Gehring", | |
| citation_graph=_empty_graph(), | |
| graph_labels=["Large Language Models"], | |
| use_project_context=True, | |
| ) | |
| assert outcome.kind == "recommendations" | |
| assert len(outcome.papers) == 1 | |
| assert outcome.papers[0]["title"] == "Newest verified work" | |
| assert calls == [("A1", {"n": 1, "year_start": None, "year_end": None, "ranking": "publication_date"})] | |
| async def test_ambiguous_author_returns_provider_backed_clarification(tmp_path): | |
| async def fake_author_resolver(name: str): | |
| return AuthorResolution( | |
| status="ambiguous", | |
| candidates=[ | |
| AuthorCandidate(openalex_id="A1", name=name, works_count=10), | |
| AuthorCandidate(openalex_id="A2", name=name, works_count=4), | |
| ], | |
| ) | |
| service = RecommendationService( | |
| author_resolver=fake_author_resolver, | |
| client=FakeClient(), | |
| root=str(tmp_path / "recommendations"), | |
| ) | |
| service.clarification_store.root = str(tmp_path / "clarifications") | |
| service.clarification_store.__init__(root=service.clarification_store.root) | |
| outcome = await service.recommend_or_clarify( | |
| project_id="project-1", | |
| query="latest paper by Alex Kim", | |
| citation_graph=_empty_graph(), | |
| graph_labels=[], | |
| use_project_context=False, | |
| ) | |
| assert outcome.kind == "clarification_required" | |
| assert [choice.resolved_values["author_id"] for choice in outcome.choices] == ["A1", "A2"] | |
| async def test_top_papers_of_year_dispatches_provider_citation_ranking(tmp_path): | |
| calls = [] | |
| async def fake_ranked_fetcher(**kwargs): | |
| calls.append(kwargs) | |
| return [{ | |
| "title": f"Ranked paper {index}", "authors": "Author", "year": 2026, | |
| "cited_by": 100 - index, "url": f"https://example.com/{index}", "abstract": "", | |
| } for index in range(5)] | |
| service = RecommendationService( | |
| ranked_fetcher=fake_ranked_fetcher, | |
| client=FakeClient(), | |
| root=str(tmp_path), | |
| ) | |
| outcome = await service.recommend_or_clarify( | |
| project_id="project-1", | |
| query="What are the top 5 papers of 2026 by citation?", | |
| citation_graph=_empty_graph(), | |
| graph_labels=[], | |
| use_project_context=False, | |
| ) | |
| assert outcome.kind == "recommendations" | |
| assert len(outcome.papers) == 5 | |
| assert calls == [{"n": 5, "year_start": 2026, "year_end": 2026, "ranking": "citation_count"}] | |
| second = await service.recommend_or_clarify( | |
| project_id="project-1", | |
| query="What are the top 5 papers of 2026 by citation?", | |
| citation_graph=_empty_graph(), | |
| graph_labels=[], | |
| use_project_context=False, | |
| ) | |
| assert len(second.papers) == 5 | |
| assert len(calls) == 1 | |
| async def test_citation_queries_dispatch_verified_direction(tmp_path, query, expected_direction): | |
| class FakeVerifiedDiscovery: | |
| def __init__(self): | |
| self.calls = [] | |
| async def discover(self, **kwargs): | |
| self.calls.append(kwargs) | |
| return VerifiedDiscoveryResult( | |
| candidates=[{ | |
| "title": "Verified related paper", "authors": "Author", "year": 2025, | |
| "cited_by": 10, "url": "https://example.com/verified", "abstract": "", | |
| "openalex_id": "W2", "citation_verified": True, | |
| }], | |
| seed=ResolvedSeed(openalex_id="W1", title="Attention Is All You Need"), | |
| exhausted=True, | |
| provider_requests=2, | |
| ) | |
| discovery = FakeVerifiedDiscovery() | |
| service = RecommendationService( | |
| verified_discovery=discovery, | |
| client=FakeClient(), | |
| root=str(tmp_path), | |
| ) | |
| outcome = await service.recommend_or_clarify( | |
| project_id="project-1", | |
| query=query, | |
| citation_graph=_empty_graph(), | |
| graph_labels=["unrelated project topic"], | |
| use_project_context=True, | |
| ) | |
| assert outcome.kind == "recommendations" | |
| assert discovery.calls[0]["seed_title"] == "Attention Is All You Need" | |
| assert discovery.calls[0]["citation_constraint"] == expected_direction | |