aiagents4pharma 1.28.0__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/main_agent.py +35 -209
- aiagents4pharma/talk2scholars/agents/s2_agent.py +10 -6
- aiagents4pharma/talk2scholars/agents/zotero_agent.py +12 -6
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml +2 -48
- 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 +1 -0
- aiagents4pharma/talk2scholars/configs/tools/__init__.py +1 -0
- aiagents4pharma/talk2scholars/configs/tools/multi_paper_recommendation/default.yaml +1 -1
- 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/tests/test_main_agent.py +186 -111
- 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/s2/multi_paper_rec.py +50 -34
- 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.28.0.dist-info → aiagents4pharma-1.29.0.dist-info}/METADATA +6 -5
- {aiagents4pharma-1.28.0.dist-info → aiagents4pharma-1.29.0.dist-info}/RECORD +37 -28
- 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.28.0.dist-info → aiagents4pharma-1.29.0.dist-info}/LICENSE +0 -0
- {aiagents4pharma-1.28.0.dist-info → aiagents4pharma-1.29.0.dist-info}/WHEEL +0 -0
- {aiagents4pharma-1.28.0.dist-info → aiagents4pharma-1.29.0.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -1,171 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Unit tests for Zotero search tool in zotero_read.py.
|
3
|
-
"""
|
4
|
-
|
5
|
-
from unittest.mock import patch
|
6
|
-
from langgraph.types import Command
|
7
|
-
from ..tools.zotero.zotero_read import zotero_search_tool
|
8
|
-
|
9
|
-
|
10
|
-
# Mock data for Zotero API response
|
11
|
-
MOCK_ZOTERO_RESPONSE = [
|
12
|
-
{
|
13
|
-
"data": {
|
14
|
-
"key": "ABC123",
|
15
|
-
"title": "Deep Learning in Medicine",
|
16
|
-
"abstractNote": "An overview of deep learning applications in medicine.",
|
17
|
-
"date": "2022",
|
18
|
-
"url": "https://example.com/paper1",
|
19
|
-
"itemType": "journalArticle",
|
20
|
-
}
|
21
|
-
},
|
22
|
-
{
|
23
|
-
"data": {
|
24
|
-
"key": "XYZ789",
|
25
|
-
"title": "Advances in AI",
|
26
|
-
"abstractNote": "Recent advancements in AI research.",
|
27
|
-
"date": "2023",
|
28
|
-
"url": "https://example.com/paper2",
|
29
|
-
"itemType": "conferencePaper",
|
30
|
-
}
|
31
|
-
},
|
32
|
-
]
|
33
|
-
|
34
|
-
|
35
|
-
class TestZoteroRead:
|
36
|
-
"""Unit tests for Zotero search tool"""
|
37
|
-
|
38
|
-
@patch("pyzotero.zotero.Zotero.items")
|
39
|
-
def test_zotero_success(self, mock_zotero):
|
40
|
-
"""Verifies successful retrieval of papers from Zotero"""
|
41
|
-
mock_zotero.return_value = MOCK_ZOTERO_RESPONSE
|
42
|
-
|
43
|
-
result = zotero_search_tool.invoke(
|
44
|
-
input={
|
45
|
-
"query": "deep learning",
|
46
|
-
"only_articles": True,
|
47
|
-
"limit": 2,
|
48
|
-
"tool_call_id": "test123",
|
49
|
-
}
|
50
|
-
)
|
51
|
-
|
52
|
-
assert isinstance(result, Command)
|
53
|
-
assert "zotero_read" in result.update
|
54
|
-
assert "messages" in result.update
|
55
|
-
papers = result.update["zotero_read"]
|
56
|
-
assert len(papers) == 2 # Should return 2 papers
|
57
|
-
assert papers["ABC123"]["Title"] == "Deep Learning in Medicine"
|
58
|
-
assert papers["XYZ789"]["Title"] == "Advances in AI"
|
59
|
-
|
60
|
-
@patch("pyzotero.zotero.Zotero.items")
|
61
|
-
def test_zotero_no_papers_found(self, mock_zotero):
|
62
|
-
"""Verifies Zotero tool behavior when no papers are found"""
|
63
|
-
mock_zotero.return_value = [] # Simulating empty response
|
64
|
-
|
65
|
-
result = zotero_search_tool.invoke(
|
66
|
-
input={
|
67
|
-
"query": "nonexistent topic",
|
68
|
-
"only_articles": True,
|
69
|
-
"limit": 2,
|
70
|
-
"tool_call_id": "test123",
|
71
|
-
}
|
72
|
-
)
|
73
|
-
|
74
|
-
assert isinstance(result, Command)
|
75
|
-
assert "zotero_read" in result.update
|
76
|
-
assert len(result.update["zotero_read"]) == 0 # No papers found
|
77
|
-
assert "messages" in result.update
|
78
|
-
assert "Number of papers found: 0" in result.update["messages"][0].content
|
79
|
-
|
80
|
-
@patch("pyzotero.zotero.Zotero.items")
|
81
|
-
def test_zotero_only_articles_filtering(self, mock_zotero):
|
82
|
-
"""Ensures only journal articles and conference papers are returned"""
|
83
|
-
mock_response = [
|
84
|
-
{
|
85
|
-
"data": {
|
86
|
-
"key": "DEF456",
|
87
|
-
"title": "A Book on AI",
|
88
|
-
"abstractNote": "Book about AI advancements.",
|
89
|
-
"date": "2021",
|
90
|
-
"url": "https://example.com/book",
|
91
|
-
"itemType": "book",
|
92
|
-
}
|
93
|
-
},
|
94
|
-
MOCK_ZOTERO_RESPONSE[0], # Valid journal article
|
95
|
-
]
|
96
|
-
mock_zotero.return_value = mock_response
|
97
|
-
|
98
|
-
result = zotero_search_tool.invoke(
|
99
|
-
input={
|
100
|
-
"query": "AI",
|
101
|
-
"only_articles": True,
|
102
|
-
"limit": 2,
|
103
|
-
"tool_call_id": "test123",
|
104
|
-
}
|
105
|
-
)
|
106
|
-
|
107
|
-
assert isinstance(result, Command)
|
108
|
-
assert "zotero_read" in result.update
|
109
|
-
papers = result.update["zotero_read"]
|
110
|
-
assert len(papers) == 1 # The book should be filtered out
|
111
|
-
assert "ABC123" in papers # Journal article should be included
|
112
|
-
|
113
|
-
@patch("pyzotero.zotero.Zotero.items")
|
114
|
-
def test_zotero_invalid_response(self, mock_zotero):
|
115
|
-
"""Tests handling of malformed API response"""
|
116
|
-
mock_zotero.return_value = [
|
117
|
-
{"data": None}, # Invalid response format
|
118
|
-
{}, # Empty object
|
119
|
-
{"data": {"title": "Missing Key", "itemType": "journalArticle"}}, # No key
|
120
|
-
]
|
121
|
-
|
122
|
-
result = zotero_search_tool.invoke(
|
123
|
-
input={
|
124
|
-
"query": "AI ethics",
|
125
|
-
"only_articles": True,
|
126
|
-
"limit": 2,
|
127
|
-
"tool_call_id": "test123",
|
128
|
-
}
|
129
|
-
)
|
130
|
-
|
131
|
-
assert isinstance(result, Command)
|
132
|
-
assert "zotero_read" in result.update
|
133
|
-
assert (
|
134
|
-
len(result.update["zotero_read"]) == 0
|
135
|
-
) # Should filter out invalid entries
|
136
|
-
assert "messages" in result.update
|
137
|
-
assert "Number of papers found: 0" in result.update["messages"][0].content
|
138
|
-
|
139
|
-
@patch("pyzotero.zotero.Zotero.items")
|
140
|
-
def test_zotero_handles_non_dict_items(self, mock_zotero):
|
141
|
-
"""Ensures that Zotero tool correctly skips non-dictionary items (covers line 86)"""
|
142
|
-
|
143
|
-
# Simulate Zotero returning an invalid item (e.g., `None` and a string)
|
144
|
-
mock_zotero.return_value = [
|
145
|
-
None,
|
146
|
-
"invalid_string",
|
147
|
-
{
|
148
|
-
"data": {
|
149
|
-
"key": "123",
|
150
|
-
"title": "Valid Paper",
|
151
|
-
"itemType": "journalArticle",
|
152
|
-
}
|
153
|
-
},
|
154
|
-
]
|
155
|
-
|
156
|
-
result = zotero_search_tool.invoke(
|
157
|
-
input={
|
158
|
-
"query": "AI ethics",
|
159
|
-
"only_articles": True,
|
160
|
-
"limit": 2,
|
161
|
-
"tool_call_id": "test123",
|
162
|
-
}
|
163
|
-
)
|
164
|
-
|
165
|
-
assert isinstance(result, Command)
|
166
|
-
assert "zotero_read" in result.update
|
167
|
-
|
168
|
-
# Expect only valid items to be processed
|
169
|
-
assert (
|
170
|
-
len(result.update["zotero_read"]) == 1
|
171
|
-
), "Only valid dictionary items should be processed"
|
File without changes
|
File without changes
|
File without changes
|