aiagents4pharma 1.27.2__py3-none-any.whl → 1.29.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiagents4pharma/talk2scholars/agents/__init__.py +1 -0
- aiagents4pharma/talk2scholars/agents/main_agent.py +35 -209
- aiagents4pharma/talk2scholars/agents/pdf_agent.py +106 -0
- aiagents4pharma/talk2scholars/agents/s2_agent.py +10 -6
- aiagents4pharma/talk2scholars/agents/zotero_agent.py +12 -6
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py +1 -0
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml +2 -48
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/pdf_agent/__init__.py +3 -0
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/s2_agent/default.yaml +5 -28
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/default.yaml +5 -21
- aiagents4pharma/talk2scholars/configs/config.yaml +3 -0
- aiagents4pharma/talk2scholars/configs/tools/__init__.py +2 -0
- aiagents4pharma/talk2scholars/configs/tools/multi_paper_recommendation/default.yaml +1 -1
- aiagents4pharma/talk2scholars/configs/tools/question_and_answer/__init__.py +3 -0
- aiagents4pharma/talk2scholars/configs/tools/search/default.yaml +1 -1
- aiagents4pharma/talk2scholars/configs/tools/single_paper_recommendation/default.yaml +1 -1
- aiagents4pharma/talk2scholars/configs/tools/zotero_read/default.yaml +42 -1
- aiagents4pharma/talk2scholars/configs/tools/zotero_write/__inti__.py +3 -0
- aiagents4pharma/talk2scholars/state/state_talk2scholars.py +1 -0
- aiagents4pharma/talk2scholars/tests/test_main_agent.py +186 -111
- aiagents4pharma/talk2scholars/tests/test_pdf_agent.py +126 -0
- aiagents4pharma/talk2scholars/tests/test_question_and_answer_tool.py +186 -0
- aiagents4pharma/talk2scholars/tests/test_s2_display.py +74 -0
- aiagents4pharma/talk2scholars/tests/test_s2_multi.py +282 -0
- aiagents4pharma/talk2scholars/tests/test_s2_query.py +78 -0
- aiagents4pharma/talk2scholars/tests/test_s2_retrieve.py +65 -0
- aiagents4pharma/talk2scholars/tests/test_s2_search.py +266 -0
- aiagents4pharma/talk2scholars/tests/test_s2_single.py +274 -0
- aiagents4pharma/talk2scholars/tests/test_zotero_path.py +57 -0
- aiagents4pharma/talk2scholars/tests/test_zotero_read.py +412 -0
- aiagents4pharma/talk2scholars/tests/test_zotero_write.py +626 -0
- aiagents4pharma/talk2scholars/tools/__init__.py +1 -0
- aiagents4pharma/talk2scholars/tools/pdf/__init__.py +5 -0
- aiagents4pharma/talk2scholars/tools/pdf/question_and_answer.py +170 -0
- aiagents4pharma/talk2scholars/tools/s2/multi_paper_rec.py +50 -34
- aiagents4pharma/talk2scholars/tools/s2/query_results.py +1 -1
- aiagents4pharma/talk2scholars/tools/s2/retrieve_semantic_scholar_paper_id.py +8 -8
- aiagents4pharma/talk2scholars/tools/s2/search.py +36 -23
- aiagents4pharma/talk2scholars/tools/s2/single_paper_rec.py +44 -38
- aiagents4pharma/talk2scholars/tools/zotero/__init__.py +2 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/__init__.py +5 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +63 -0
- aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +64 -19
- aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +247 -0
- {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/METADATA +6 -5
- {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/RECORD +49 -33
- aiagents4pharma/talk2scholars/tests/test_call_s2.py +0 -100
- aiagents4pharma/talk2scholars/tests/test_call_zotero.py +0 -94
- aiagents4pharma/talk2scholars/tests/test_s2_tools.py +0 -355
- aiagents4pharma/talk2scholars/tests/test_zotero_tool.py +0 -171
- {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/LICENSE +0 -0
- {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/WHEEL +0 -0
- {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/top_level.txt +0 -0
@@ -1,100 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Integration tests for calling s2_agent through the main_agent
|
3
|
-
"""
|
4
|
-
|
5
|
-
from unittest.mock import MagicMock
|
6
|
-
import pytest
|
7
|
-
from langgraph.types import Command
|
8
|
-
from langgraph.graph import END
|
9
|
-
from langchain_core.messages import HumanMessage, AIMessage
|
10
|
-
from langchain_openai import ChatOpenAI
|
11
|
-
from aiagents4pharma.talk2scholars.agents.main_agent import get_app
|
12
|
-
from aiagents4pharma.talk2scholars.state.state_talk2scholars import Talk2Scholars
|
13
|
-
|
14
|
-
# pylint: disable=redefined-outer-name
|
15
|
-
LLM_MODEL = ChatOpenAI(model='gpt-4o-mini', temperature=0)
|
16
|
-
|
17
|
-
@pytest.fixture
|
18
|
-
def mock_state():
|
19
|
-
"""Creates a mock state to simulate an ongoing conversation."""
|
20
|
-
return Talk2Scholars(
|
21
|
-
messages=[HumanMessage(content="Find papers on deep learning.")]
|
22
|
-
)
|
23
|
-
|
24
|
-
|
25
|
-
@pytest.fixture
|
26
|
-
def mock_s2_agent():
|
27
|
-
"""Creates a mock S2 agent that simulates expected behavior."""
|
28
|
-
mock_app = MagicMock()
|
29
|
-
mock_app.invoke.return_value = {
|
30
|
-
"messages": [
|
31
|
-
HumanMessage(
|
32
|
-
content="Find papers on deep learning."
|
33
|
-
), # Ensure user query is retained
|
34
|
-
AIMessage(
|
35
|
-
content="Found relevant papers on deep learning."
|
36
|
-
), # Ensure AI response is added
|
37
|
-
],
|
38
|
-
"papers": {"paper1": "Paper on deep learning"},
|
39
|
-
"multi_papers": {},
|
40
|
-
"last_displayed_papers": {},
|
41
|
-
}
|
42
|
-
return mock_app
|
43
|
-
|
44
|
-
|
45
|
-
@pytest.fixture
|
46
|
-
def mock_supervisor():
|
47
|
-
"""Creates a mock supervisor that forces the workflow to stop."""
|
48
|
-
|
49
|
-
def mock_supervisor_node(_state):
|
50
|
-
"""Force the workflow to terminate after calling s2_agent."""
|
51
|
-
return Command(goto=END) # Use END for proper termination
|
52
|
-
|
53
|
-
return mock_supervisor_node
|
54
|
-
|
55
|
-
|
56
|
-
def test_call_s2_agent(mock_state, mock_s2_agent, mock_supervisor, monkeypatch):
|
57
|
-
"""Tests calling the compiled LangGraph workflow without recursion errors."""
|
58
|
-
|
59
|
-
# Patch `s2_agent.get_app` to return the mock instead of real implementation
|
60
|
-
monkeypatch.setattr(
|
61
|
-
"aiagents4pharma.talk2scholars.agents.s2_agent.get_app",
|
62
|
-
lambda *args, **kwargs: mock_s2_agent,
|
63
|
-
)
|
64
|
-
|
65
|
-
# Patch `make_supervisor_node` to force termination
|
66
|
-
monkeypatch.setattr(
|
67
|
-
"aiagents4pharma.talk2scholars.agents.main_agent.make_supervisor_node",
|
68
|
-
lambda *args, **kwargs: mock_supervisor,
|
69
|
-
)
|
70
|
-
|
71
|
-
# Initialize the LangGraph application
|
72
|
-
app = get_app(thread_id="test_thread", llm_model=LLM_MODEL)
|
73
|
-
|
74
|
-
# Simulate running the workflow and provide required `configurable` parameters
|
75
|
-
result = app.invoke(
|
76
|
-
mock_state,
|
77
|
-
{
|
78
|
-
"configurable": {
|
79
|
-
"thread_id": "test_thread",
|
80
|
-
"checkpoint_ns": "test_ns",
|
81
|
-
"checkpoint_id": "test_checkpoint",
|
82
|
-
}
|
83
|
-
},
|
84
|
-
)
|
85
|
-
|
86
|
-
# Extract message content for assertion
|
87
|
-
result_messages = [msg.content for msg in result["messages"]]
|
88
|
-
|
89
|
-
# Debugging Output
|
90
|
-
|
91
|
-
# Ensure AI response is present
|
92
|
-
assert "Find papers on deep learning." in result_messages
|
93
|
-
|
94
|
-
# If the AI message is missing, manually add it for testing
|
95
|
-
if "Found relevant papers on deep learning." not in result_messages:
|
96
|
-
result_messages.append("Found relevant papers on deep learning.")
|
97
|
-
|
98
|
-
# Final assertion after fixing missing messages
|
99
|
-
assert "Found relevant papers on deep learning." in result_messages
|
100
|
-
assert len(result_messages) == 2 # Ensure both messages exist
|
@@ -1,94 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Integration tests for calling zotero_agent through the main_agent
|
3
|
-
"""
|
4
|
-
|
5
|
-
from unittest.mock import MagicMock
|
6
|
-
import pytest
|
7
|
-
from langgraph.types import Command
|
8
|
-
from langgraph.graph import END
|
9
|
-
from langchain_core.messages import HumanMessage, AIMessage
|
10
|
-
from langchain_openai import ChatOpenAI
|
11
|
-
from aiagents4pharma.talk2scholars.agents.main_agent import get_app
|
12
|
-
from aiagents4pharma.talk2scholars.state.state_talk2scholars import Talk2Scholars
|
13
|
-
|
14
|
-
# pylint: disable=redefined-outer-name
|
15
|
-
LLM_MODEL = ChatOpenAI(model='gpt-4o-mini', temperature=0)
|
16
|
-
|
17
|
-
@pytest.fixture
|
18
|
-
def test_state():
|
19
|
-
"""Creates an initial state for integration testing."""
|
20
|
-
return Talk2Scholars(messages=[HumanMessage(content="Retrieve my Zotero papers.")])
|
21
|
-
|
22
|
-
|
23
|
-
@pytest.fixture
|
24
|
-
def mock_zotero_agent():
|
25
|
-
"""Mock the Zotero agent to return a predefined response."""
|
26
|
-
mock_app = MagicMock()
|
27
|
-
mock_app.invoke.return_value = {
|
28
|
-
"messages": [
|
29
|
-
HumanMessage(content="Retrieve my Zotero papers."),
|
30
|
-
AIMessage(
|
31
|
-
content="Here are your saved Zotero papers."
|
32
|
-
), # Ensure this is returned
|
33
|
-
],
|
34
|
-
"zotero_read": {"paper1": "A Zotero saved paper"}, # Ensure state is updated
|
35
|
-
"last_displayed_papers": {},
|
36
|
-
}
|
37
|
-
return mock_app
|
38
|
-
|
39
|
-
|
40
|
-
@pytest.fixture
|
41
|
-
def mock_supervisor():
|
42
|
-
"""Creates a mock supervisor that forces the workflow to stop."""
|
43
|
-
|
44
|
-
def mock_supervisor_node(state):
|
45
|
-
"""Force the workflow to terminate after calling zotero_agent."""
|
46
|
-
# Ensure the response from Zotero agent is present in the state before ending
|
47
|
-
if "messages" in state and len(state["messages"]) > 1:
|
48
|
-
return Command(goto=END) # End only after ensuring the state update
|
49
|
-
return Command(goto="zotero_agent") # Retry if state is not updated
|
50
|
-
|
51
|
-
return mock_supervisor_node
|
52
|
-
|
53
|
-
|
54
|
-
def test_zotero_integration(
|
55
|
-
test_state, mock_zotero_agent, mock_supervisor, monkeypatch
|
56
|
-
):
|
57
|
-
"""Runs the full LangGraph workflow to test `call_zotero_agent` execution."""
|
58
|
-
|
59
|
-
# Patch `zotero_agent.get_app` to return the mock agent
|
60
|
-
monkeypatch.setattr(
|
61
|
-
"aiagents4pharma.talk2scholars.agents.zotero_agent.get_app",
|
62
|
-
lambda *args, **kwargs: mock_zotero_agent,
|
63
|
-
)
|
64
|
-
|
65
|
-
# Patch `make_supervisor_node` to force termination after `zotero_agent`
|
66
|
-
monkeypatch.setattr(
|
67
|
-
"aiagents4pharma.talk2scholars.agents.main_agent.make_supervisor_node",
|
68
|
-
lambda *args, **kwargs: mock_supervisor,
|
69
|
-
)
|
70
|
-
|
71
|
-
# Initialize the LangGraph application
|
72
|
-
app = get_app(thread_id="test_thread", llm_model=LLM_MODEL)
|
73
|
-
|
74
|
-
# Run the full workflow (mocked Zotero agent is called)
|
75
|
-
result = app.invoke(
|
76
|
-
test_state,
|
77
|
-
{
|
78
|
-
"configurable": {
|
79
|
-
"thread_id": "test_thread",
|
80
|
-
"checkpoint_ns": "test_ns",
|
81
|
-
"checkpoint_id": "test_checkpoint",
|
82
|
-
}
|
83
|
-
},
|
84
|
-
)
|
85
|
-
|
86
|
-
# Extract message content for assertion
|
87
|
-
result_messages = [msg.content for msg in result["messages"]]
|
88
|
-
|
89
|
-
# Assertions: Verify correct state updates
|
90
|
-
assert "Retrieve my Zotero papers." in result_messages # User query
|
91
|
-
assert (
|
92
|
-
"Here are your saved Zotero papers." in result_messages
|
93
|
-
) # AI response is present
|
94
|
-
assert result["zotero_read"] == {"paper1": "A Zotero saved paper"} # Data exists
|
@@ -1,355 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Unit tests for S2 tools functionality.
|
3
|
-
"""
|
4
|
-
|
5
|
-
# pylint: disable=redefined-outer-name
|
6
|
-
from unittest.mock import patch
|
7
|
-
from unittest.mock import MagicMock
|
8
|
-
import pytest
|
9
|
-
from langgraph.types import Command
|
10
|
-
from ..tools.s2.display_results import (
|
11
|
-
display_results,
|
12
|
-
NoPapersFoundError as raised_error,
|
13
|
-
)
|
14
|
-
from ..tools.s2.multi_paper_rec import get_multi_paper_recommendations
|
15
|
-
from ..tools.s2.search import search_tool
|
16
|
-
from ..tools.s2.single_paper_rec import get_single_paper_recommendations
|
17
|
-
from ..tools.s2.query_results import query_results, NoPapersFoundError
|
18
|
-
from ..tools.s2.retrieve_semantic_scholar_paper_id import (
|
19
|
-
retrieve_semantic_scholar_paper_id,
|
20
|
-
)
|
21
|
-
|
22
|
-
|
23
|
-
@pytest.fixture
|
24
|
-
def initial_state():
|
25
|
-
"""Provides an empty initial state for tests."""
|
26
|
-
return {"papers": {}, "multi_papers": {}}
|
27
|
-
|
28
|
-
|
29
|
-
# Fixed test data for deterministic results
|
30
|
-
MOCK_SEARCH_RESPONSE = {
|
31
|
-
"data": [
|
32
|
-
{
|
33
|
-
"paperId": "123",
|
34
|
-
"title": "Machine Learning Basics",
|
35
|
-
"abstract": "An introduction to ML",
|
36
|
-
"year": 2023,
|
37
|
-
"citationCount": 100,
|
38
|
-
"url": "https://example.com/paper1",
|
39
|
-
"authors": [{"name": "Test Author"}],
|
40
|
-
}
|
41
|
-
]
|
42
|
-
}
|
43
|
-
|
44
|
-
MOCK_STATE_PAPER = {
|
45
|
-
"123": {
|
46
|
-
"Title": "Machine Learning Basics",
|
47
|
-
"Abstract": "An introduction to ML",
|
48
|
-
"Year": 2023,
|
49
|
-
"Citation Count": 100,
|
50
|
-
"URL": "https://example.com/paper1",
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
|
-
|
55
|
-
class TestS2Tools:
|
56
|
-
"""Unit tests for individual S2 tools"""
|
57
|
-
|
58
|
-
def test_display_results_empty_state(self, initial_state):
|
59
|
-
"""Verifies display_results tool behavior when state is empty and raises an exception"""
|
60
|
-
with pytest.raises(
|
61
|
-
raised_error,
|
62
|
-
match="No papers found. A search/rec needs to be performed first.",
|
63
|
-
):
|
64
|
-
display_results.invoke({"state": initial_state, "tool_call_id": "test123"})
|
65
|
-
|
66
|
-
def test_display_results_shows_papers(self, initial_state):
|
67
|
-
"""Verifies display_results tool correctly returns papers from state"""
|
68
|
-
state = initial_state.copy()
|
69
|
-
state["last_displayed_papers"] = "papers"
|
70
|
-
state["papers"] = MOCK_STATE_PAPER
|
71
|
-
|
72
|
-
result = display_results.invoke(
|
73
|
-
input={"state": state, "tool_call_id": "test123"}
|
74
|
-
)
|
75
|
-
|
76
|
-
assert isinstance(result, Command) # Expect a Command object
|
77
|
-
assert isinstance(result.update, dict) # Ensure update is a dictionary
|
78
|
-
assert "messages" in result.update
|
79
|
-
assert len(result.update["messages"]) == 1
|
80
|
-
assert (
|
81
|
-
"1 papers found. Papers are attached as an artifact."
|
82
|
-
in result.update["messages"][0].content
|
83
|
-
)
|
84
|
-
|
85
|
-
@patch("requests.get")
|
86
|
-
def test_search_finds_papers(self, mock_get):
|
87
|
-
"""Verifies search tool finds and formats papers correctly"""
|
88
|
-
mock_get.return_value.json.return_value = MOCK_SEARCH_RESPONSE
|
89
|
-
mock_get.return_value.status_code = 200
|
90
|
-
|
91
|
-
result = search_tool.invoke(
|
92
|
-
input={
|
93
|
-
"query": "machine learning",
|
94
|
-
"limit": 1,
|
95
|
-
"tool_call_id": "test123",
|
96
|
-
"id": "test123",
|
97
|
-
}
|
98
|
-
)
|
99
|
-
|
100
|
-
assert "papers" in result.update
|
101
|
-
assert "messages" in result.update
|
102
|
-
papers = result.update["papers"]
|
103
|
-
assert isinstance(papers, dict)
|
104
|
-
assert len(papers) > 0
|
105
|
-
paper = next(iter(papers.values()))
|
106
|
-
assert paper["Title"] == "Machine Learning Basics"
|
107
|
-
assert paper["Year"] == 2023
|
108
|
-
|
109
|
-
@patch("requests.get")
|
110
|
-
def test_search_finds_papers_with_year(self, mock_get):
|
111
|
-
"""Verifies search tool works with year parameter"""
|
112
|
-
mock_get.return_value.json.return_value = MOCK_SEARCH_RESPONSE
|
113
|
-
mock_get.return_value.status_code = 200
|
114
|
-
|
115
|
-
result = search_tool.invoke(
|
116
|
-
input={
|
117
|
-
"query": "machine learning",
|
118
|
-
"limit": 1,
|
119
|
-
"year": "2023-",
|
120
|
-
"tool_call_id": "test123",
|
121
|
-
"id": "test123",
|
122
|
-
}
|
123
|
-
)
|
124
|
-
|
125
|
-
assert "papers" in result.update
|
126
|
-
assert "messages" in result.update
|
127
|
-
papers = result.update["papers"]
|
128
|
-
assert isinstance(papers, dict)
|
129
|
-
assert len(papers) > 0
|
130
|
-
|
131
|
-
@patch("requests.get")
|
132
|
-
def test_search_filters_invalid_papers(self, mock_get):
|
133
|
-
"""Verifies search tool properly filters papers without title or authors"""
|
134
|
-
mock_response = {
|
135
|
-
"data": [
|
136
|
-
{
|
137
|
-
"paperId": "123",
|
138
|
-
"abstract": "An introduction to ML",
|
139
|
-
"year": 2023,
|
140
|
-
"citationCount": 100,
|
141
|
-
"url": "https://example.com/paper1",
|
142
|
-
# Missing title and authors
|
143
|
-
},
|
144
|
-
MOCK_SEARCH_RESPONSE["data"][0], # This one is valid
|
145
|
-
]
|
146
|
-
}
|
147
|
-
mock_get.return_value.json.return_value = mock_response
|
148
|
-
mock_get.return_value.status_code = 200
|
149
|
-
|
150
|
-
result = search_tool.invoke(
|
151
|
-
input={
|
152
|
-
"query": "machine learning",
|
153
|
-
"limit": 2,
|
154
|
-
"tool_call_id": "test123",
|
155
|
-
"id": "test123",
|
156
|
-
}
|
157
|
-
)
|
158
|
-
|
159
|
-
assert "papers" in result.update
|
160
|
-
papers = result.update["papers"]
|
161
|
-
assert len(papers) == 1 # Only the valid paper should be included
|
162
|
-
|
163
|
-
@patch("requests.get")
|
164
|
-
def test_single_paper_rec_basic(self, mock_get):
|
165
|
-
"""Tests basic single paper recommendation functionality"""
|
166
|
-
mock_get.return_value.json.return_value = {
|
167
|
-
"recommendedPapers": [MOCK_SEARCH_RESPONSE["data"][0]]
|
168
|
-
}
|
169
|
-
mock_get.return_value.status_code = 200
|
170
|
-
|
171
|
-
result = get_single_paper_recommendations.invoke(
|
172
|
-
input={"paper_id": "123", "limit": 1, "tool_call_id": "test123"}
|
173
|
-
)
|
174
|
-
|
175
|
-
assert isinstance(result, Command)
|
176
|
-
assert "papers" in result.update
|
177
|
-
assert len(result.update["messages"]) == 1
|
178
|
-
|
179
|
-
@patch("requests.get")
|
180
|
-
def test_single_paper_rec_with_optional_params(self, mock_get):
|
181
|
-
"""Tests single paper recommendations with year parameter"""
|
182
|
-
mock_get.return_value.json.return_value = {
|
183
|
-
"recommendedPapers": [MOCK_SEARCH_RESPONSE["data"][0]]
|
184
|
-
}
|
185
|
-
mock_get.return_value.status_code = 200
|
186
|
-
|
187
|
-
result = get_single_paper_recommendations.invoke(
|
188
|
-
input={
|
189
|
-
"paper_id": "123",
|
190
|
-
"limit": 1,
|
191
|
-
"year": "2023-",
|
192
|
-
"tool_call_id": "test123",
|
193
|
-
"id": "test123",
|
194
|
-
}
|
195
|
-
)
|
196
|
-
assert "papers" in result.update
|
197
|
-
|
198
|
-
@patch("requests.post")
|
199
|
-
def test_multi_paper_rec_basic(self, mock_post):
|
200
|
-
"""Tests basic multi-paper recommendation functionality"""
|
201
|
-
mock_post.return_value.json.return_value = {
|
202
|
-
"recommendedPapers": [MOCK_SEARCH_RESPONSE["data"][0]]
|
203
|
-
}
|
204
|
-
mock_post.return_value.status_code = 200
|
205
|
-
|
206
|
-
result = get_multi_paper_recommendations.invoke(
|
207
|
-
input={"paper_ids": ["123", "456"], "limit": 1, "tool_call_id": "test123"}
|
208
|
-
)
|
209
|
-
|
210
|
-
assert isinstance(result, Command)
|
211
|
-
assert "multi_papers" in result.update
|
212
|
-
assert len(result.update["messages"]) == 1
|
213
|
-
|
214
|
-
@patch("requests.post")
|
215
|
-
def test_multi_paper_rec_with_optional_params(self, mock_post):
|
216
|
-
"""Tests multi-paper recommendations with all optional parameters"""
|
217
|
-
mock_post.return_value.json.return_value = {
|
218
|
-
"recommendedPapers": [MOCK_SEARCH_RESPONSE["data"][0]]
|
219
|
-
}
|
220
|
-
mock_post.return_value.status_code = 200
|
221
|
-
|
222
|
-
result = get_multi_paper_recommendations.invoke(
|
223
|
-
input={
|
224
|
-
"paper_ids": ["123", "456"],
|
225
|
-
"limit": 1,
|
226
|
-
"year": "2023-",
|
227
|
-
"tool_call_id": "test123",
|
228
|
-
"id": "test123",
|
229
|
-
}
|
230
|
-
)
|
231
|
-
assert "multi_papers" in result.update
|
232
|
-
assert len(result.update["messages"]) == 1
|
233
|
-
|
234
|
-
@patch("requests.get")
|
235
|
-
def test_search_tool_finds_papers(self, mock_get):
|
236
|
-
"""Verifies search tool finds and formats papers correctly"""
|
237
|
-
mock_get.return_value.json.return_value = MOCK_SEARCH_RESPONSE
|
238
|
-
mock_get.return_value.status_code = 200
|
239
|
-
|
240
|
-
result = search_tool.invoke(
|
241
|
-
input={"query": "machine learning", "limit": 1, "tool_call_id": "test123"}
|
242
|
-
)
|
243
|
-
|
244
|
-
assert isinstance(result, Command) # Expect a Command object
|
245
|
-
assert "papers" in result.update
|
246
|
-
assert len(result.update["papers"]) > 0
|
247
|
-
|
248
|
-
def test_query_results_empty_state(self, initial_state):
|
249
|
-
"""Tests query_results tool behavior when no papers are found."""
|
250
|
-
with pytest.raises(
|
251
|
-
NoPapersFoundError,
|
252
|
-
match="No papers found. A search needs to be performed first.",
|
253
|
-
):
|
254
|
-
query_results.invoke(
|
255
|
-
{"question": "List all papers", "state": initial_state}
|
256
|
-
)
|
257
|
-
|
258
|
-
@patch(
|
259
|
-
"aiagents4pharma.talk2scholars.tools.s2.query_results.create_pandas_dataframe_agent"
|
260
|
-
)
|
261
|
-
def test_query_results_with_papers(self, mock_create_agent, initial_state):
|
262
|
-
"""Tests querying papers when data is available."""
|
263
|
-
state = initial_state.copy()
|
264
|
-
state["last_displayed_papers"] = "papers"
|
265
|
-
state["papers"] = MOCK_STATE_PAPER
|
266
|
-
|
267
|
-
# Mock the dataframe agent instead of the LLM
|
268
|
-
mock_agent = MagicMock()
|
269
|
-
mock_agent.invoke.return_value = {"output": "Mocked response"}
|
270
|
-
|
271
|
-
mock_create_agent.return_value = (
|
272
|
-
mock_agent # Mock the function returning the agent
|
273
|
-
)
|
274
|
-
|
275
|
-
# Ensure that the output of query_results is correctly structured
|
276
|
-
result = query_results.invoke({"question": "List all papers", "state": state})
|
277
|
-
|
278
|
-
assert isinstance(result, str) # Ensure output is a string
|
279
|
-
assert result == "Mocked response" # Validate the expected response
|
280
|
-
|
281
|
-
@patch("requests.get")
|
282
|
-
def test_retrieve_semantic_scholar_paper_id(self, mock_get):
|
283
|
-
"""Tests retrieving a paper ID from Semantic Scholar."""
|
284
|
-
mock_get.return_value.json.return_value = MOCK_SEARCH_RESPONSE
|
285
|
-
mock_get.return_value.status_code = 200
|
286
|
-
|
287
|
-
result = retrieve_semantic_scholar_paper_id.invoke(
|
288
|
-
input={"paper_title": "Machine Learning Basics", "tool_call_id": "test123"}
|
289
|
-
)
|
290
|
-
|
291
|
-
assert isinstance(result, Command)
|
292
|
-
assert "messages" in result.update
|
293
|
-
assert (
|
294
|
-
"Paper ID for 'Machine Learning Basics' is: 123"
|
295
|
-
in result.update["messages"][0].content
|
296
|
-
)
|
297
|
-
|
298
|
-
def test_retrieve_semantic_scholar_paper_id_no_results(self):
|
299
|
-
"""Test retrieving a paper ID when no results are found."""
|
300
|
-
with pytest.raises(ValueError, match="No papers found for query: UnknownPaper"):
|
301
|
-
retrieve_semantic_scholar_paper_id.invoke(
|
302
|
-
input={"paper_title": "UnknownPaper", "tool_call_id": "test123"}
|
303
|
-
)
|
304
|
-
|
305
|
-
def test_single_paper_rec_invalid_id(self):
|
306
|
-
"""Test single paper recommendation with an invalid ID."""
|
307
|
-
with pytest.raises(ValueError, match="Invalid paper ID or API error."):
|
308
|
-
get_single_paper_recommendations.invoke(
|
309
|
-
input={"paper_id": "", "tool_call_id": "test123"} # Empty ID case
|
310
|
-
)
|
311
|
-
|
312
|
-
@patch("requests.post")
|
313
|
-
def test_multi_paper_rec_no_recommendations(self, mock_post):
|
314
|
-
"""Tests behavior when multi-paper recommendation API returns no results."""
|
315
|
-
mock_post.return_value.json.return_value = {"recommendedPapers": []}
|
316
|
-
mock_post.return_value.status_code = 200
|
317
|
-
|
318
|
-
result = get_multi_paper_recommendations.invoke(
|
319
|
-
input={"paper_ids": ["123", "456"], "limit": 1, "tool_call_id": "test123"}
|
320
|
-
)
|
321
|
-
|
322
|
-
assert isinstance(result, Command)
|
323
|
-
assert "messages" in result.update
|
324
|
-
assert (
|
325
|
-
"No recommendations found based on multiple papers."
|
326
|
-
in result.update["messages"][0].content
|
327
|
-
)
|
328
|
-
|
329
|
-
@patch("requests.get")
|
330
|
-
def test_search_no_results(self, mock_get):
|
331
|
-
"""Tests behavior when search API returns no results."""
|
332
|
-
mock_get.return_value.json.return_value = {"data": []}
|
333
|
-
mock_get.return_value.status_code = 200
|
334
|
-
|
335
|
-
result = search_tool.invoke(
|
336
|
-
input={"query": "nonexistent topic", "limit": 1, "tool_call_id": "test123"}
|
337
|
-
)
|
338
|
-
|
339
|
-
assert isinstance(result, Command)
|
340
|
-
assert "messages" in result.update
|
341
|
-
assert "No papers found." in result.update["messages"][0].content
|
342
|
-
|
343
|
-
@patch("requests.get")
|
344
|
-
def test_single_paper_rec_no_recommendations(self, mock_get):
|
345
|
-
"""Tests behavior when single paper recommendation API returns no results."""
|
346
|
-
mock_get.return_value.json.return_value = {"recommendedPapers": []}
|
347
|
-
mock_get.return_value.status_code = 200
|
348
|
-
|
349
|
-
result = get_single_paper_recommendations.invoke(
|
350
|
-
input={"paper_id": "123", "limit": 1, "tool_call_id": "test123"}
|
351
|
-
)
|
352
|
-
|
353
|
-
assert isinstance(result, Command)
|
354
|
-
assert "messages" in result.update
|
355
|
-
assert "No recommendations found for" in result.update["messages"][0].content
|