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.
Files changed (53) hide show
  1. aiagents4pharma/talk2scholars/agents/__init__.py +1 -0
  2. aiagents4pharma/talk2scholars/agents/main_agent.py +35 -209
  3. aiagents4pharma/talk2scholars/agents/pdf_agent.py +106 -0
  4. aiagents4pharma/talk2scholars/agents/s2_agent.py +10 -6
  5. aiagents4pharma/talk2scholars/agents/zotero_agent.py +12 -6
  6. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py +1 -0
  7. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml +2 -48
  8. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/pdf_agent/__init__.py +3 -0
  9. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/s2_agent/default.yaml +5 -28
  10. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/default.yaml +5 -21
  11. aiagents4pharma/talk2scholars/configs/config.yaml +3 -0
  12. aiagents4pharma/talk2scholars/configs/tools/__init__.py +2 -0
  13. aiagents4pharma/talk2scholars/configs/tools/multi_paper_recommendation/default.yaml +1 -1
  14. aiagents4pharma/talk2scholars/configs/tools/question_and_answer/__init__.py +3 -0
  15. aiagents4pharma/talk2scholars/configs/tools/search/default.yaml +1 -1
  16. aiagents4pharma/talk2scholars/configs/tools/single_paper_recommendation/default.yaml +1 -1
  17. aiagents4pharma/talk2scholars/configs/tools/zotero_read/default.yaml +42 -1
  18. aiagents4pharma/talk2scholars/configs/tools/zotero_write/__inti__.py +3 -0
  19. aiagents4pharma/talk2scholars/state/state_talk2scholars.py +1 -0
  20. aiagents4pharma/talk2scholars/tests/test_main_agent.py +186 -111
  21. aiagents4pharma/talk2scholars/tests/test_pdf_agent.py +126 -0
  22. aiagents4pharma/talk2scholars/tests/test_question_and_answer_tool.py +186 -0
  23. aiagents4pharma/talk2scholars/tests/test_s2_display.py +74 -0
  24. aiagents4pharma/talk2scholars/tests/test_s2_multi.py +282 -0
  25. aiagents4pharma/talk2scholars/tests/test_s2_query.py +78 -0
  26. aiagents4pharma/talk2scholars/tests/test_s2_retrieve.py +65 -0
  27. aiagents4pharma/talk2scholars/tests/test_s2_search.py +266 -0
  28. aiagents4pharma/talk2scholars/tests/test_s2_single.py +274 -0
  29. aiagents4pharma/talk2scholars/tests/test_zotero_path.py +57 -0
  30. aiagents4pharma/talk2scholars/tests/test_zotero_read.py +412 -0
  31. aiagents4pharma/talk2scholars/tests/test_zotero_write.py +626 -0
  32. aiagents4pharma/talk2scholars/tools/__init__.py +1 -0
  33. aiagents4pharma/talk2scholars/tools/pdf/__init__.py +5 -0
  34. aiagents4pharma/talk2scholars/tools/pdf/question_and_answer.py +170 -0
  35. aiagents4pharma/talk2scholars/tools/s2/multi_paper_rec.py +50 -34
  36. aiagents4pharma/talk2scholars/tools/s2/query_results.py +1 -1
  37. aiagents4pharma/talk2scholars/tools/s2/retrieve_semantic_scholar_paper_id.py +8 -8
  38. aiagents4pharma/talk2scholars/tools/s2/search.py +36 -23
  39. aiagents4pharma/talk2scholars/tools/s2/single_paper_rec.py +44 -38
  40. aiagents4pharma/talk2scholars/tools/zotero/__init__.py +2 -0
  41. aiagents4pharma/talk2scholars/tools/zotero/utils/__init__.py +5 -0
  42. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +63 -0
  43. aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +64 -19
  44. aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +247 -0
  45. {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/METADATA +6 -5
  46. {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/RECORD +49 -33
  47. aiagents4pharma/talk2scholars/tests/test_call_s2.py +0 -100
  48. aiagents4pharma/talk2scholars/tests/test_call_zotero.py +0 -94
  49. aiagents4pharma/talk2scholars/tests/test_s2_tools.py +0 -355
  50. aiagents4pharma/talk2scholars/tests/test_zotero_tool.py +0 -171
  51. {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/LICENSE +0 -0
  52. {aiagents4pharma-1.27.2.dist-info → aiagents4pharma-1.29.0.dist-info}/WHEEL +0 -0
  53. {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