aiagents4pharma 1.30.2__py3-none-any.whl → 1.30.4__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 (38) hide show
  1. aiagents4pharma/talk2scholars/__init__.py +2 -0
  2. aiagents4pharma/talk2scholars/agents/__init__.py +8 -0
  3. aiagents4pharma/talk2scholars/configs/__init__.py +2 -0
  4. aiagents4pharma/talk2scholars/configs/agents/__init__.py +2 -0
  5. aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py +2 -0
  6. aiagents4pharma/talk2scholars/configs/app/__init__.py +2 -0
  7. aiagents4pharma/talk2scholars/configs/tools/__init__.py +9 -0
  8. aiagents4pharma/talk2scholars/state/__init__.py +4 -2
  9. aiagents4pharma/talk2scholars/tests/test_s2_multi.py +10 -8
  10. aiagents4pharma/talk2scholars/tests/test_s2_search.py +9 -5
  11. aiagents4pharma/talk2scholars/tests/test_s2_single.py +7 -7
  12. aiagents4pharma/talk2scholars/tests/test_zotero_path.py +25 -11
  13. aiagents4pharma/talk2scholars/tests/test_zotero_read.py +49 -35
  14. aiagents4pharma/talk2scholars/tests/test_zotero_write.py +10 -10
  15. aiagents4pharma/talk2scholars/tools/__init__.py +3 -0
  16. aiagents4pharma/talk2scholars/tools/pdf/__init__.py +4 -2
  17. aiagents4pharma/talk2scholars/tools/s2/__init__.py +9 -0
  18. aiagents4pharma/talk2scholars/tools/s2/multi_paper_rec.py +9 -135
  19. aiagents4pharma/talk2scholars/tools/s2/search.py +8 -114
  20. aiagents4pharma/talk2scholars/tools/s2/single_paper_rec.py +8 -126
  21. aiagents4pharma/talk2scholars/tools/s2/utils/__init__.py +7 -0
  22. aiagents4pharma/talk2scholars/tools/s2/utils/multi_helper.py +194 -0
  23. aiagents4pharma/talk2scholars/tools/s2/utils/search_helper.py +175 -0
  24. aiagents4pharma/talk2scholars/tools/s2/utils/single_helper.py +186 -0
  25. aiagents4pharma/talk2scholars/tools/zotero/__init__.py +2 -0
  26. aiagents4pharma/talk2scholars/tools/zotero/utils/__init__.py +5 -0
  27. aiagents4pharma/talk2scholars/tools/zotero/utils/read_helper.py +167 -0
  28. aiagents4pharma/talk2scholars/tools/zotero/utils/review_helper.py +78 -0
  29. aiagents4pharma/talk2scholars/tools/zotero/utils/write_helper.py +197 -0
  30. aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +1 -1
  31. aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +9 -136
  32. aiagents4pharma/talk2scholars/tools/zotero/zotero_review.py +14 -48
  33. aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +22 -147
  34. {aiagents4pharma-1.30.2.dist-info → aiagents4pharma-1.30.4.dist-info}/METADATA +1 -1
  35. {aiagents4pharma-1.30.2.dist-info → aiagents4pharma-1.30.4.dist-info}/RECORD +38 -31
  36. {aiagents4pharma-1.30.2.dist-info → aiagents4pharma-1.30.4.dist-info}/WHEEL +0 -0
  37. {aiagents4pharma-1.30.2.dist-info → aiagents4pharma-1.30.4.dist-info}/licenses/LICENSE +0 -0
  38. {aiagents4pharma-1.30.2.dist-info → aiagents4pharma-1.30.4.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,18 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
3
  """
4
- multi_paper_rec: Tool for getting recommendations
5
- based on multiple papers
4
+ This tool is used to return recommendations based on multiple papers
6
5
  """
7
6
 
8
- import json
9
7
  import logging
10
8
  from typing import Annotated, Any, List, Optional
11
- import hydra
12
- import requests
13
9
  from langchain_core.messages import ToolMessage
14
10
  from langchain_core.tools import tool
15
11
  from langchain_core.tools.base import InjectedToolCallId
16
12
  from langgraph.types import Command
17
13
  from pydantic import BaseModel, Field
14
+ from .utils.multi_helper import MultiPaperRecData
18
15
 
19
- # pylint: disable=R0914,R0912,R0915
20
16
 
21
17
  # Configure logging
22
18
  logging.basicConfig(level=logging.INFO)
@@ -66,143 +62,21 @@ def get_multi_paper_recommendations(
66
62
  Returns:
67
63
  Dict[str, Any]: The recommendations and related information.
68
64
  """
69
- # Load hydra configuration
70
- with hydra.initialize(version_base=None, config_path="../../configs"):
71
- cfg = hydra.compose(
72
- config_name="config", overrides=["tools/multi_paper_recommendation=default"]
73
- )
74
- cfg = cfg.tools.multi_paper_recommendation
75
- logger.info("Loaded configuration for multi-paper recommendation tool")
76
- logger.info(
77
- "Starting multi-paper recommendations search with paper IDs: %s", paper_ids
78
- )
79
-
80
- endpoint = cfg.api_endpoint
81
- headers = cfg.headers
82
- payload = {"positivePaperIds": paper_ids, "negativePaperIds": []}
83
- params = {
84
- "limit": min(limit, 500),
85
- "fields": ",".join(cfg.api_fields),
86
- }
87
-
88
- # Add year parameter if provided
89
- if year:
90
- params["year"] = year
91
-
92
- # Wrap API call in try/except to catch connectivity issues and validate response format
93
- response = None
94
- for attempt in range(10):
95
- try:
96
- response = requests.post(
97
- endpoint,
98
- headers=headers,
99
- params=params,
100
- data=json.dumps(payload),
101
- timeout=cfg.request_timeout,
102
- )
103
- response.raise_for_status() # Raises HTTPError for bad responses
104
- break # Exit loop if request is successful
105
- except requests.exceptions.RequestException as e:
106
- logger.error(
107
- "Attempt %d: Failed to connect to Semantic Scholar API for "
108
- "multi-paper recommendations: %s",
109
- attempt + 1,
110
- e,
111
- )
112
- if attempt == 9: # Last attempt
113
- raise RuntimeError(
114
- "Failed to connect to Semantic Scholar API after 10 attempts."
115
- "Please retry the same query."
116
- ) from e
117
-
118
- if response is None:
119
- raise RuntimeError("Failed to obtain a response from the Semantic Scholar API.")
120
-
121
- logger.info(
122
- "API Response Status for multi-paper recommendations: %s", response.status_code
123
- )
124
- logger.info("Request params: %s", params)
125
-
126
- data = response.json()
127
-
128
- # Check for expected data format
129
- if "recommendedPapers" not in data:
130
- logger.error("Unexpected API response format: %s", data)
131
- raise RuntimeError(
132
- "Unexpected response from Semantic Scholar API. The results could not be "
133
- "retrieved due to an unexpected format. "
134
- "Please modify your search query and try again."
135
- )
65
+ # Create recommendation data object to organize variables
66
+ rec_data = MultiPaperRecData(paper_ids, limit, year, tool_call_id)
136
67
 
137
- recommendations = data.get("recommendedPapers", [])
138
- if not recommendations:
139
- logger.error(
140
- "No recommendations returned from API for paper IDs: %s", paper_ids
141
- )
142
- raise RuntimeError(
143
- "No recommendations were found for your query. Consider refining your search "
144
- "by using more specific keywords or different terms."
145
- )
146
-
147
- # Create a dictionary to store the papers
148
- filtered_papers = {
149
- paper["paperId"]: {
150
- "semantic_scholar_paper_id": paper["paperId"],
151
- "Title": paper.get("title", "N/A"),
152
- "Abstract": paper.get("abstract", "N/A"),
153
- "Year": paper.get("year", "N/A"),
154
- "Publication Date": paper.get("publicationDate", "N/A"),
155
- "Venue": paper.get("venue", "N/A"),
156
- # "Publication Venue": (paper.get("publicationVenue") or {}).get("name", "N/A"),
157
- # "Venue Type": (paper.get("publicationVenue") or {}).get("name", "N/A"),
158
- "Journal Name": (paper.get("journal") or {}).get("name", "N/A"),
159
- # "Journal Volume": paper.get("journal", {}).get("volume", "N/A"),
160
- # "Journal Pages": paper.get("journal", {}).get("pages", "N/A"),
161
- "Citation Count": paper.get("citationCount", "N/A"),
162
- "Authors": [
163
- f"{author.get('name', 'N/A')} (ID: {author.get('authorId', 'N/A')})"
164
- for author in paper.get("authors", [])
165
- ],
166
- "URL": paper.get("url", "N/A"),
167
- "arxiv_id": paper.get("externalIds", {}).get("ArXiv", "N/A"),
168
- }
169
- for paper in recommendations
170
- if paper.get("title") and paper.get("authors")
171
- }
172
-
173
- # Prepare content with top 3 paper titles and years
174
- top_papers = list(filtered_papers.values())[:3]
175
- top_papers_info = "\n".join(
176
- [
177
- # f"{i+1}. {paper['Title']} ({paper['Year']})"
178
- f"{i+1}. {paper['Title']} ({paper['Year']}; "
179
- f"semantic_scholar_paper_id: {paper['semantic_scholar_paper_id']}; "
180
- f"arXiv ID: {paper['arxiv_id']})"
181
- for i, paper in enumerate(top_papers)
182
- ]
183
- )
184
-
185
- logger.info("Filtered %d papers", len(filtered_papers))
186
-
187
- content = (
188
- "Recommendations based on multiple papers were successful. "
189
- "Papers are attached as an artifact."
190
- )
191
- content += " Here is a summary of the recommendations:\n"
192
- content += f"Number of recommended papers found: {len(filtered_papers)}\n"
193
- content += f"Query Paper IDs: {', '.join(paper_ids)}\n"
194
- content += f"Year: {year}\n" if year else ""
195
- content += "Here are a few of these papers:\n" + top_papers_info
68
+ # Process the recommendations
69
+ results = rec_data.process_recommendations()
196
70
 
197
71
  return Command(
198
72
  update={
199
- "multi_papers": filtered_papers, # Sending the dictionary directly
73
+ "multi_papers": results["papers"],
200
74
  "last_displayed_papers": "multi_papers",
201
75
  "messages": [
202
76
  ToolMessage(
203
- content=content,
77
+ content=results["content"],
204
78
  tool_call_id=tool_call_id,
205
- artifact=filtered_papers,
79
+ artifact=results["papers"],
206
80
  )
207
81
  ],
208
82
  }
@@ -6,15 +6,13 @@ This tool is used to search for academic papers on Semantic Scholar.
6
6
 
7
7
  import logging
8
8
  from typing import Annotated, Any, Optional
9
- import hydra
10
- import requests
11
9
  from langchain_core.messages import ToolMessage
12
10
  from langchain_core.tools import tool
13
11
  from langchain_core.tools.base import InjectedToolCallId
14
12
  from langgraph.types import Command
15
13
  from pydantic import BaseModel, Field
14
+ from .utils.search_helper import SearchData
16
15
 
17
- # pylint: disable=R0914,R0912,R0915
18
16
  # Configure logging
19
17
  logging.basicConfig(level=logging.INFO)
20
18
  logger = logging.getLogger(__name__)
@@ -58,125 +56,21 @@ def search_tool(
58
56
  Returns:
59
57
  The number of papers found on Semantic Scholar.
60
58
  """
61
- # Load hydra configuration
62
- with hydra.initialize(version_base=None, config_path="../../configs"):
63
- cfg = hydra.compose(config_name="config", overrides=["tools/search=default"])
64
- cfg = cfg.tools.search
65
- logger.info("Loaded configuration for search tool")
66
- logger.info("Searching for papers on %s", query)
67
- endpoint = cfg.api_endpoint
68
- params = {
69
- "query": query,
70
- "limit": min(limit, 100),
71
- "fields": ",".join(cfg.api_fields),
72
- }
59
+ # Create search data object to organize variables
60
+ search_data = SearchData(query, limit, year, tool_call_id)
73
61
 
74
- # Add year parameter if provided
75
- if year:
76
- params["year"] = year
77
-
78
- # Wrap API call in try/except to catch connectivity issues
79
- response = None
80
- for attempt in range(10):
81
- try:
82
- response = requests.get(endpoint, params=params, timeout=10)
83
- response.raise_for_status() # Raises HTTPError for bad responses
84
- break # Exit loop if request is successful
85
- except requests.exceptions.RequestException as e:
86
- logger.error(
87
- "Attempt %d: Failed to connect to Semantic Scholar API: %s",
88
- attempt + 1,
89
- e,
90
- )
91
- if attempt == 9: # Last attempt
92
- raise RuntimeError(
93
- "Failed to connect to Semantic Scholar API after 10 attempts."
94
- "Please retry the same query."
95
- ) from e
96
-
97
- if response is None:
98
- raise RuntimeError("Failed to obtain a response from the Semantic Scholar API.")
99
-
100
- data = response.json()
101
-
102
- # Check for expected data format
103
- if "data" not in data:
104
- logger.error("Unexpected API response format: %s", data)
105
- raise RuntimeError(
106
- "Unexpected response from Semantic Scholar API. The results could not be "
107
- "retrieved due to an unexpected format. "
108
- "Please modify your search query and try again."
109
- )
110
-
111
- papers = data.get("data", [])
112
- if not papers:
113
- logger.error(
114
- "No papers returned from Semantic Scholar API for query: %s", query
115
- )
116
- raise RuntimeError(
117
- "No papers were found for your query. Consider refining your search "
118
- "by using more specific keywords or different terms."
119
- )
120
-
121
- # Create a dictionary to store the papers
122
- filtered_papers = {
123
- paper["paperId"]: {
124
- "semantic_scholar_paper_id": paper["paperId"],
125
- "Title": paper.get("title", "N/A"),
126
- "Abstract": paper.get("abstract", "N/A"),
127
- "Year": paper.get("year", "N/A"),
128
- "Publication Date": paper.get("publicationDate", "N/A"),
129
- "Venue": paper.get("venue", "N/A"),
130
- # "Publication Venue": (paper.get("publicationVenue") or {}).get("name", "N/A"),
131
- # "Venue Type": (paper.get("publicationVenue") or {}).get("name", "N/A"),
132
- "Journal Name": (paper.get("journal") or {}).get("name", "N/A"),
133
- # "Journal Volume": paper.get("journal", {}).get("volume", "N/A"),
134
- # "Journal Pages": paper.get("journal", {}).get("pages", "N/A"),
135
- "Citation Count": paper.get("citationCount", "N/A"),
136
- "Authors": [
137
- f"{author.get('name', 'N/A')} (ID: {author.get('authorId', 'N/A')})"
138
- for author in paper.get("authors", [])
139
- ],
140
- "URL": paper.get("url", "N/A"),
141
- "arxiv_id": paper.get("externalIds", {}).get("ArXiv", "N/A"),
142
- }
143
- for paper in papers
144
- if paper.get("title") and paper.get("authors")
145
- }
146
-
147
- logger.info("Filtered %d papers", len(filtered_papers))
148
-
149
- # Prepare content with top 3 paper titles and years
150
- top_papers = list(filtered_papers.values())[:3]
151
- top_papers_info = "\n".join(
152
- [
153
- f"{i+1}. {paper['Title']} ({paper['Year']}; "
154
- f"semantic_scholar_paper_id: {paper['semantic_scholar_paper_id']}; "
155
- f"arXiv ID: {paper['arxiv_id']})"
156
- for i, paper in enumerate(top_papers)
157
- ]
158
- )
159
-
160
- logger.info("-----------Filtered %d papers", len(filtered_papers))
161
-
162
- content = (
163
- "Search was successful. Papers are attached as an artifact. "
164
- "Here is a summary of the search results:\n"
165
- )
166
- content += f"Number of papers found: {len(filtered_papers)}\n"
167
- content += f"Query: {query}\n"
168
- content += f"Year: {year}\n" if year else ""
169
- content += "Top 3 papers:\n" + top_papers_info
62
+ # Process the search
63
+ results = search_data.process_search()
170
64
 
171
65
  return Command(
172
66
  update={
173
- "papers": filtered_papers, # Sending the dictionary directly
67
+ "papers": results["papers"],
174
68
  "last_displayed_papers": "papers",
175
69
  "messages": [
176
70
  ToolMessage(
177
- content=content,
71
+ content=results["content"],
178
72
  tool_call_id=tool_call_id,
179
- artifact=filtered_papers,
73
+ artifact=results["papers"],
180
74
  )
181
75
  ],
182
76
  }
@@ -6,15 +6,13 @@ This tool is used to return recommendations for a single paper.
6
6
 
7
7
  import logging
8
8
  from typing import Annotated, Any, Optional
9
- import hydra
10
- import requests
11
9
  from langchain_core.messages import ToolMessage
12
10
  from langchain_core.tools import tool
13
11
  from langchain_core.tools.base import InjectedToolCallId
14
12
  from langgraph.types import Command
15
13
  from pydantic import BaseModel, Field
14
+ from .utils.single_helper import SinglePaperRecData
16
15
 
17
- # pylint: disable=R0914,R0912,R0915
18
16
  # Configure logging
19
17
  logging.basicConfig(level=logging.INFO)
20
18
  logger = logging.getLogger(__name__)
@@ -62,137 +60,21 @@ def get_single_paper_recommendations(
62
60
  Returns:
63
61
  Dict[str, Any]: The recommendations and related information.
64
62
  """
65
- # Load hydra configuration
66
- with hydra.initialize(version_base=None, config_path="../../configs"):
67
- cfg = hydra.compose(
68
- config_name="config",
69
- overrides=["tools/single_paper_recommendation=default"],
70
- )
71
- cfg = cfg.tools.single_paper_recommendation
72
- logger.info("Loaded configuration for single paper recommendation tool")
73
- logger.info(
74
- "Starting single paper recommendations search with paper ID: %s", paper_id
75
- )
76
-
77
- endpoint = f"{cfg.api_endpoint}/{paper_id}"
78
- params = {
79
- "limit": min(limit, 500), # Max 500 per API docs
80
- "fields": ",".join(cfg.api_fields),
81
- "from": cfg.recommendation_params.from_pool,
82
- }
83
-
84
- # Add year parameter if provided
85
- if year:
86
- params["year"] = year
87
-
88
- # Wrap API call in try/except to catch connectivity issues and check response format
89
- response = None
90
- for attempt in range(10):
91
- try:
92
- response = requests.get(
93
- endpoint, params=params, timeout=cfg.request_timeout
94
- )
95
- response.raise_for_status() # Raises HTTPError for bad responses
96
- break # Exit loop if request is successful
97
- except requests.exceptions.RequestException as e:
98
- logger.error(
99
- "Attempt %d: Failed to connect to Semantic Scholar API for recommendations: %s",
100
- attempt + 1,
101
- e,
102
- )
103
- if attempt == 9: # Last attempt
104
- raise RuntimeError(
105
- "Failed to connect to Semantic Scholar API after 10 attempts."
106
- "Please retry the same query."
107
- ) from e
108
-
109
- if response is None:
110
- raise RuntimeError("Failed to obtain a response from the Semantic Scholar API.")
111
-
112
- logger.info(
113
- "API Response Status for recommendations of paper %s: %s",
114
- paper_id,
115
- response.status_code,
116
- )
117
- logger.info("Request params: %s", params)
118
-
119
- data = response.json()
120
-
121
- # Check for expected data format
122
- if "recommendedPapers" not in data:
123
- logger.error("Unexpected API response format: %s", data)
124
- raise RuntimeError(
125
- "Unexpected response from Semantic Scholar API. The results could not be "
126
- "retrieved due to an unexpected format. "
127
- "Please modify your search query and try again."
128
- )
63
+ # Create recommendation data object to organize variables
64
+ rec_data = SinglePaperRecData(paper_id, limit, year, tool_call_id)
129
65
 
130
- recommendations = data.get("recommendedPapers", [])
131
- if not recommendations:
132
- logger.error("No recommendations returned from API for paper: %s", paper_id)
133
- raise RuntimeError(
134
- "No recommendations were found for your query. Consider refining your search "
135
- "by using more specific keywords or different terms."
136
- )
137
-
138
- # Extract paper ID and title from recommendations
139
- filtered_papers = {
140
- paper["paperId"]: {
141
- "semantic_scholar_paper_id": paper["paperId"],
142
- "Title": paper.get("title", "N/A"),
143
- "Abstract": paper.get("abstract", "N/A"),
144
- "Year": paper.get("year", "N/A"),
145
- "Publication Date": paper.get("publicationDate", "N/A"),
146
- "Venue": paper.get("venue", "N/A"),
147
- # "Publication Venue": (paper.get("publicationVenue") or {}).get("name", "N/A"),
148
- # "Venue Type": (paper.get("publicationVenue") or {}).get("name", "N/A"),
149
- "Journal Name": (paper.get("journal") or {}).get("name", "N/A"),
150
- # "Journal Volume": paper.get("journal", {}).get("volume", "N/A"),
151
- # "Journal Pages": paper.get("journal", {}).get("pages", "N/A"),
152
- "Citation Count": paper.get("citationCount", "N/A"),
153
- "Authors": [
154
- f"{author.get('name', 'N/A')} (ID: {author.get('authorId', 'N/A')})"
155
- for author in paper.get("authors", [])
156
- ],
157
- "URL": paper.get("url", "N/A"),
158
- "arxiv_id": paper.get("externalIds", {}).get("ArXiv", "N/A"),
159
- }
160
- for paper in recommendations
161
- if paper.get("title") and paper.get("authors")
162
- }
163
-
164
- # Prepare content with top 3 paper titles and years
165
- top_papers = list(filtered_papers.values())[:3]
166
- top_papers_info = "\n".join(
167
- [
168
- # f"{i+1}. {paper['Title']} ({paper['Year']})"
169
- f"{i+1}. {paper['Title']} ({paper['Year']}; "
170
- f"semantic_scholar_paper_id: {paper['semantic_scholar_paper_id']}; "
171
- f"arXiv ID: {paper['arxiv_id']})"
172
- for i, paper in enumerate(top_papers)
173
- ]
174
- )
175
-
176
- logger.info("Filtered %d papers", len(filtered_papers))
177
-
178
- content = (
179
- "Recommendations based on the single paper were successful. "
180
- "Papers are attached as an artifact. "
181
- "Here is a summary of the recommendations:\n"
182
- )
183
- content += f"Number of recommended papers found: {len(filtered_papers)}\n"
184
- content += f"Query Paper ID: {paper_id}\n"
185
- content += "Here are a few of these papers:\n" + top_papers_info
66
+ # Process the recommendations
67
+ results = rec_data.process_recommendations()
186
68
 
187
69
  return Command(
188
70
  update={
189
- "papers": filtered_papers, # Sending the dictionary directly
71
+ "papers": results["papers"],
190
72
  "last_displayed_papers": "papers",
191
73
  "messages": [
192
74
  ToolMessage(
193
- content=content,
75
+ content=results["content"],
194
76
  tool_call_id=tool_call_id,
195
- artifact=filtered_papers,
77
+ artifact=results["papers"],
196
78
  )
197
79
  ],
198
80
  }
@@ -0,0 +1,7 @@
1
+ """This module contains utility functions for the Semantic Scholar search tool."""
2
+
3
+ from . import search_helper
4
+ from . import single_helper
5
+ from . import multi_helper
6
+
7
+ __all__ = ["search_helper", "single_helper", "multi_helper"]