aiagents4pharma 1.30.1__py3-none-any.whl → 1.30.3__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/__init__.py +2 -0
- aiagents4pharma/talk2scholars/agents/__init__.py +8 -0
- aiagents4pharma/talk2scholars/agents/zotero_agent.py +9 -7
- aiagents4pharma/talk2scholars/configs/__init__.py +2 -0
- aiagents4pharma/talk2scholars/configs/agents/__init__.py +2 -0
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py +2 -0
- aiagents4pharma/talk2scholars/configs/agents/talk2scholars/zotero_agent/default.yaml +9 -15
- aiagents4pharma/talk2scholars/configs/app/__init__.py +2 -0
- aiagents4pharma/talk2scholars/configs/tools/__init__.py +9 -0
- aiagents4pharma/talk2scholars/configs/tools/zotero_write/default.yaml +55 -0
- aiagents4pharma/talk2scholars/state/__init__.py +4 -2
- aiagents4pharma/talk2scholars/state/state_talk2scholars.py +3 -0
- aiagents4pharma/talk2scholars/tests/test_routing_logic.py +1 -2
- aiagents4pharma/talk2scholars/tests/test_s2_multi.py +10 -8
- aiagents4pharma/talk2scholars/tests/test_s2_search.py +9 -5
- aiagents4pharma/talk2scholars/tests/test_s2_single.py +7 -7
- aiagents4pharma/talk2scholars/tests/test_zotero_agent.py +3 -2
- aiagents4pharma/talk2scholars/tests/test_zotero_human_in_the_loop.py +273 -0
- aiagents4pharma/talk2scholars/tests/test_zotero_path.py +433 -1
- aiagents4pharma/talk2scholars/tests/test_zotero_read.py +57 -43
- aiagents4pharma/talk2scholars/tests/test_zotero_write.py +123 -588
- aiagents4pharma/talk2scholars/tools/__init__.py +3 -0
- aiagents4pharma/talk2scholars/tools/pdf/__init__.py +4 -2
- aiagents4pharma/talk2scholars/tools/s2/__init__.py +9 -0
- aiagents4pharma/talk2scholars/tools/s2/multi_paper_rec.py +9 -135
- aiagents4pharma/talk2scholars/tools/s2/search.py +8 -114
- aiagents4pharma/talk2scholars/tools/s2/single_paper_rec.py +8 -126
- aiagents4pharma/talk2scholars/tools/s2/utils/__init__.py +7 -0
- aiagents4pharma/talk2scholars/tools/s2/utils/multi_helper.py +194 -0
- aiagents4pharma/talk2scholars/tools/s2/utils/search_helper.py +175 -0
- aiagents4pharma/talk2scholars/tools/s2/utils/single_helper.py +186 -0
- aiagents4pharma/talk2scholars/tools/zotero/__init__.py +3 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/__init__.py +5 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/read_helper.py +167 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/review_helper.py +78 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/write_helper.py +197 -0
- aiagents4pharma/talk2scholars/tools/zotero/utils/zotero_path.py +126 -1
- aiagents4pharma/talk2scholars/tools/zotero/zotero_read.py +10 -139
- aiagents4pharma/talk2scholars/tools/zotero/zotero_review.py +164 -0
- aiagents4pharma/talk2scholars/tools/zotero/zotero_write.py +40 -229
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.3.dist-info}/METADATA +3 -2
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.3.dist-info}/RECORD +45 -35
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.3.dist-info}/WHEEL +1 -1
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.3.dist-info/licenses}/LICENSE +0 -0
- {aiagents4pharma-1.30.1.dist-info → aiagents4pharma-1.30.3.dist-info}/top_level.txt +0 -0
@@ -8,3 +8,12 @@ from . import search
|
|
8
8
|
from . import single_paper_rec
|
9
9
|
from . import query_results
|
10
10
|
from . import retrieve_semantic_scholar_paper_id
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
"display_results",
|
14
|
+
"multi_paper_rec",
|
15
|
+
"search",
|
16
|
+
"single_paper_rec",
|
17
|
+
"query_results",
|
18
|
+
"retrieve_semantic_scholar_paper_id",
|
19
|
+
]
|
@@ -1,22 +1,18 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
|
3
3
|
"""
|
4
|
-
|
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
|
-
#
|
70
|
-
|
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
|
-
|
138
|
-
|
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":
|
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=
|
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
|
-
#
|
62
|
-
|
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
|
-
#
|
75
|
-
|
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":
|
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=
|
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
|
-
#
|
66
|
-
|
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
|
-
|
131
|
-
|
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":
|
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=
|
77
|
+
artifact=results["papers"],
|
196
78
|
)
|
197
79
|
],
|
198
80
|
}
|