Skip to content

Commit 8085e9a

Browse files
ansh-infoapoorva-infodmccloskey
authored
feat: talk2scholars zotero read (#110)
* feat: openai test for calling s2 agent * feat: openai test for calling s2 agent * feat: updated main agent test after zotero integartion * feat: test for zotero_agent * feat: test for zotero_tool * feat: added zotero agent Co-authored-by: Apoorva Gupta <apoorvaagupta.info@gmail.com> * feat: added zotero read tool Co-authored-by: Apoorva Gupta <apoorvaagupta.info@gmail.com> * chores: updated init files * feat: added key for zotero read * feat: updated path for zotero configs * feat: config for zotero tool * chores: updated system prompt for main and s2 agent * feat: system prompt for zotero agent * feat: added zotero agent routing for the manin agent * fix: dependency issue with pyzotero and ollama * fix: updated doc stings for zotero agent * feat: added zotero for mkdocs * chores: instructions for zotero env variables * chores: path for zotero hydra configs * chores: updated readme * chores: hashed config * fix: added pasing the artifacts * fix: added tests for zotero tool * feat: added retrive semantic scholar id tool * chores: updated system prompt * chores: updated comment * Update aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml Co-authored-by: Douglas McCloskey <dmccloskey@users.noreply.github.com> * fix: updated system prompt for agent s2 * fix: updated system prompt for agent zotero * fix: updated system prompt for main agent * feat: Prepare content with top 3 paper, to prevent llm halucinations * feat: Prepare content with top 3 paper, to prevent llm halucinations * feat: added filter types to hydra config * fix: updated tests to use direct query matching * feat: synonym variations into the test suite * feat: added fromating for date and year * fix: updated tests to check if the Zotero response and data are correctly added to the conversation * fix: pip packages, github actions issue * chores: added pyzotero package --------- Co-authored-by: Apoorva Gupta <apoorvaagupta.info@gmail.com> Co-authored-by: Douglas McCloskey <dmccloskey@users.noreply.github.com>
1 parent 81d4822 commit 8085e9a

34 files changed

Lines changed: 1167 additions & 102 deletions

README.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2FVirtualPatientEngine%2FAIAgents4Pharma%2Frefs%2Fheads%2Fmain%2Fpyproject.toml)
77
![Docker Pulls](https://img.shields.io/docker/pulls/virtualpatientengine/talk2biomodels?link=https%3A%2F%2Fhub.docker.com%2Frepository%2Fdocker%2Fvirtualpatientengine%2Ftalk2biomodels%2Fgeneral)
88

9-
109
## Introduction
1110

1211
Welcome to **AIAgents4Pharma** – an open-source project by [Team VPE](https://github.com/VirtualPatientEngine) that brings together AI-driven tools to help researchers and pharma interact seamlessly with complex biological data.
@@ -46,8 +45,8 @@ _Please note that this option is currently available only for Talk2Biomodels._
4645
```
4746
docker run -e OPENAI_API_KEY=<openai_api_key> -e NVIDIA_API_KEY=<nvidia_api_key> -p 8501:8501 virtualpatientengine/talk2biomodels
4847
```
49-
_You can create a free account at NVIDIA and apply for their
50-
free credits [here](https://build.nvidia.com/explore/discover)._
48+
_You can create a free account at NVIDIA and apply for their
49+
free credits [here](https://build.nvidia.com/explore/discover)._
5150

5251
#### Option 3: git
5352

@@ -61,31 +60,43 @@ free credits [here](https://build.nvidia.com/explore/discover)._
6160
pip install -r requirements.txt
6261
```
6362
3. **Initialize OPENAI_API_KEY and NVIDIA_API_KEY**
63+
6464
```bash
6565
export OPENAI_API_KEY=....
6666
```
67+
6768
```bash
6869
export NVIDIA_API_KEY=....
6970
```
70-
_You can create a free account at NVIDIA and apply for their
71-
free credits [here](https://build.nvidia.com/explore/discover)._
7271

73-
4. **[Optional] Initialize LANGSMITH_API_KEY**
72+
_You can create a free account at NVIDIA and apply for their
73+
free credits [here](https://build.nvidia.com/explore/discover)._
74+
75+
4. **Initialize ZOTERO_API_KEY and ZOTERO_USER_ID**
76+
77+
```bash
78+
export ZOTERO_API_KEY=....
79+
export ZOTERO_USER_ID=....
80+
```
81+
82+
5. **[Optional] Initialize LANGSMITH_API_KEY**
83+
7484
```bash
7585
export LANGCHAIN_TRACING_V2=true
7686
export LANGCHAIN_API_KEY=<your-api-key>
7787
```
78-
_Please note that this will create a new tracing project in your Langsmith
79-
account with the name `T2X-xxxx`, where `X` can be `B` (Biomodels), `S` (Scholars),
80-
`KG` (KnowledgeGraphs), or `C` (Cells). If you skip the previous step, it will
81-
default to the name `default`. `xxxx` will be the 4-digit ID created for the
82-
session._
8388

84-
5. **Launch the app:**
89+
_Please note that this will create a new tracing project in your Langsmith
90+
account with the name `T2X-xxxx`, where `X` can be `B` (Biomodels), `S` (Scholars),
91+
`KG` (KnowledgeGraphs), or `C` (Cells). If you skip the previous step, it will
92+
default to the name `default`. `xxxx` will be the 4-digit ID created for the
93+
session._
94+
95+
6. **Launch the app:**
8596
```bash
8697
streamlit run app/frontend/streamlit_app_<agent>.py
8798
```
88-
_Replace <agent> with the agent name you are interested to launch._
99+
_Replace <agent> with the agent name you are interested to launch._
89100

90101
For detailed instructions on each agent, please refer to their respective modules.
91102

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
'''
1+
"""
22
This file is used to import all the modules in the package.
3-
'''
3+
"""
44

55
from . import main_agent
66
from . import s2_agent
7+
from . import zotero_agent

aiagents4pharma/talk2scholars/agents/main_agent.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from langgraph.graph import END, START, StateGraph
2020
from langgraph.types import Command
2121
from ..agents import s2_agent
22+
from ..agents import zotero_agent
2223
from ..state.state_talk2scholars import Talk2Scholars
2324

2425
# Configure logging
@@ -49,7 +50,7 @@ def make_supervisor_node(llm_model: BaseChatModel, thread_id: str) -> Callable:
4950
5051
This function initializes the routing logic by leveraging the system and router prompts defined
5152
in the Hydra configuration. The supervisor determines whether to
52-
call a sub-agent (like `s2_agent`)
53+
call a sub-agent (like `s2_agent`, `zotero_agent`)
5354
or directly generate a response using the language model.
5455
5556
Args:
@@ -62,12 +63,12 @@ def make_supervisor_node(llm_model: BaseChatModel, thread_id: str) -> Callable:
6263
"""
6364
cfg = get_hydra_config()
6465
logger.info("Hydra configuration for Talk2Scholars main agent loaded: %s", cfg)
65-
members = ["s2_agent"]
66+
members = ["s2_agent", "zotero_agent"]
6667
options = ["FINISH"] + members
6768
# Define system prompt for general interactions
6869
system_prompt = cfg.system_prompt
6970
# Define router prompt for routing to sub-agents
70-
router_prompt = cfg.router_prompt
71+
router_prompt = cfg.router_prompt + " " + " ".join(members)
7172

7273
class Router(BaseModel):
7374
"""Worker to route to next. If no workers needed, route to FINISH."""
@@ -90,7 +91,7 @@ def supervisor_node(
9091
Returns:
9192
Command: A command dictating whether to invoke a sub-agent or generate a final response.
9293
"""
93-
messages = [SystemMessage(content=router_prompt)] + state["messages"]
94+
messages = [SystemMessage(content=router_prompt)] + list(state["messages"])
9495
structured_llm = llm_model.with_structured_output(Router)
9596
response = structured_llm.invoke(messages)
9697
goto = response.next
@@ -189,6 +190,51 @@ def call_s2_agent(
189190
goto="supervisor",
190191
)
191192

193+
def call_zotero_agent(
194+
state: Talk2Scholars,
195+
) -> Command[Literal["supervisor"]]:
196+
"""
197+
Invokes the Zotero agent to retrieve and process papers from the user's Zotero library.
198+
199+
This function calls the Zotero agent, which interacts with the user's Zotero database
200+
to retrieve relevant papers based on the conversation context. It updates the
201+
conversation state with the retrieved papers and relevant metadata.
202+
203+
Args:
204+
state (Talk2Scholars): The current conversation state, containing user messages
205+
and any previously retrieved Zotero data.
206+
207+
Returns:
208+
Command: A command that updates the conversation state with retrieved Zotero
209+
papers and metadata before returning control to the supervisor node.
210+
211+
Example:
212+
>>> result = call_zotero_agent(current_state)
213+
>>> next_step = result.goto
214+
"""
215+
logger.info("Calling Zotero agent")
216+
app = zotero_agent.get_app(thread_id, llm_model)
217+
# Invoke the Zotero agent, passing state
218+
response = app.invoke(
219+
state,
220+
{
221+
"configurable": {
222+
"config_id": thread_id,
223+
"thread_id": thread_id,
224+
}
225+
},
226+
)
227+
logger.info("Zotero agent completed with response")
228+
return Command(
229+
update={
230+
"messages": response["messages"],
231+
"zotero_read": response.get("zotero_read", {}),
232+
"last_displayed_papers": response.get("last_displayed_papers", {}),
233+
},
234+
# Always return to supervisor
235+
goto="supervisor",
236+
)
237+
192238
# Initialize LLM
193239
logger.info("Using model %s with temperature %s", llm_model, cfg.temperature)
194240

@@ -198,6 +244,7 @@ def call_s2_agent(
198244
# Add nodes
199245
workflow.add_node("supervisor", supervisor)
200246
workflow.add_node("s2_agent", call_s2_agent)
247+
workflow.add_node("zotero_agent", call_zotero_agent)
201248
# Add edges
202249
workflow.add_edge(START, "supervisor")
203250
# Compile the workflow
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Agent for interacting with Zotero
5+
"""
6+
7+
import logging
8+
from typing import Any, Dict
9+
import hydra
10+
from langchain_openai import ChatOpenAI
11+
from langchain_core.language_models.chat_models import BaseChatModel
12+
from langgraph.graph import START, StateGraph
13+
from langgraph.prebuilt import create_react_agent, ToolNode
14+
from langgraph.checkpoint.memory import MemorySaver
15+
from ..state.state_talk2scholars import Talk2Scholars
16+
from ..tools.zotero.zotero_read import zotero_search_tool
17+
from ..tools.s2.display_results import display_results as s2_display
18+
from ..tools.s2.query_results import query_results as s2_query_results
19+
from ..tools.s2.retrieve_semantic_scholar_paper_id import (
20+
retrieve_semantic_scholar_paper_id,
21+
)
22+
23+
# Initialize logger
24+
logging.basicConfig(level=logging.INFO)
25+
logger = logging.getLogger(__name__)
26+
27+
28+
def get_app(
29+
uniq_id, llm_model: BaseChatModel = ChatOpenAI(model="gpt-4o-mini", temperature=0)
30+
):
31+
"""
32+
Initializes and returns the LangGraph application for the Zotero agent.
33+
34+
This function sets up the Zotero agent, which integrates various tools to search,
35+
retrieve, and display research papers from Zotero. The agent follows the ReAct
36+
pattern for structured interaction.
37+
38+
Args:
39+
uniq_id (str): Unique identifier for the current conversation session.
40+
llm_model (BaseChatModel, optional): The language model to be used by the agent.
41+
Defaults to `ChatOpenAI(model="gpt-4o-mini", temperature=0)`.
42+
43+
Returns:
44+
StateGraph: A compiled LangGraph application that enables the Zotero agent to
45+
process user queries and retrieve research papers.
46+
47+
Example:
48+
>>> app = get_app("thread_123")
49+
>>> result = app.invoke(initial_state)
50+
"""
51+
52+
def agent_zotero_node(state: Talk2Scholars) -> Dict[str, Any]:
53+
"""
54+
Processes the user query and retrieves relevant research papers from Zotero.
55+
56+
This function calls the language model using the configured `ReAct` agent to
57+
analyze the state and generate an appropriate response. The function then
58+
returns control to the main supervisor.
59+
60+
Args:
61+
state (Talk2Scholars): The current conversation state, including messages exchanged
62+
and any previously retrieved research papers.
63+
64+
Returns:
65+
Dict[str, Any]: A dictionary containing the updated conversation state.
66+
67+
Example:
68+
>>> result = agent_zotero_node(current_state)
69+
>>> papers = result.get("papers", [])
70+
"""
71+
logger.log(
72+
logging.INFO, "Creating Agent_Zotero node with thread_id %s", uniq_id
73+
)
74+
result = model.invoke(state, {"configurable": {"thread_id": uniq_id}})
75+
76+
return result
77+
78+
# Load hydra configuration
79+
logger.log(logging.INFO, "Load Hydra configuration for Talk2Scholars Zotero agent.")
80+
with hydra.initialize(version_base=None, config_path="../configs"):
81+
cfg = hydra.compose(
82+
config_name="config",
83+
overrides=["agents/talk2scholars/zotero_agent=default"],
84+
)
85+
cfg = cfg.agents.talk2scholars.zotero_agent
86+
87+
# Define the tools
88+
tools = ToolNode(
89+
[
90+
zotero_search_tool,
91+
s2_display,
92+
s2_query_results,
93+
retrieve_semantic_scholar_paper_id,
94+
]
95+
)
96+
97+
# Define the model
98+
logger.log(logging.INFO, "Using model %s", llm_model)
99+
100+
# Create the agent
101+
model = create_react_agent(
102+
llm_model,
103+
tools=tools,
104+
state_schema=Talk2Scholars,
105+
state_modifier=cfg.zotero_agent,
106+
checkpointer=MemorySaver(),
107+
)
108+
109+
workflow = StateGraph(Talk2Scholars)
110+
workflow.add_node("agent_zotero", agent_zotero_node)
111+
workflow.add_edge(START, "agent_zotero")
112+
113+
# Initialize memory to persist state between graph runs
114+
checkpointer = MemorySaver()
115+
116+
# Compile the graph
117+
app = workflow.compile(checkpointer=checkpointer)
118+
logger.log(logging.INFO, "Compiled the graph")
119+
120+
return app

aiagents4pharma/talk2scholars/configs/agents/talk2scholars/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
from . import s2_agent
66
from . import main_agent
7+
from . import zotero_agent

aiagents4pharma/talk2scholars/configs/agents/talk2scholars/main_agent/default.yaml

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,50 @@ system_prompt: >
1010
1111
You have access to the following agents:
1212
1. S2_agent: This agent can be used to search and recommend papers
13-
from Semantic Scholar. Use this agent when the user asks for
14-
general paper searches and recommendations. This agent can also
15-
retrieve the Semantic Scholar ID of a paper.
13+
from Semantic Scholar. Use this agent when the user asks for
14+
general paper searches and recommendations.
15+
2. Zotero_agent: This agent can be used to retrieve, display, and query
16+
papers from the Zotero library. Use this agent only when the user
17+
explicitly asks for papers from Zotero.
18+
1619
router_prompt: >
1720
You are a supervisor tasked with managing a conversation between the
18-
following workers: {members}. Given the user request, respond with the
21+
following workers/members: ["s2_agent", "zotero_agent"] Given the user request, respond with the
1922
worker to act next. Each worker will perform a task and respond with
2023
their results and status. When finished, respond with FINISH.
2124
2225
Here is a description of the workers:
2326
1. S2_agent: This agent can be used to search and recommend papers
24-
from Semantic Scholar. Use this agent when the user asks for
25-
general paper searches and recommendations. This agent can also
26-
retrieve the Semantic Scholar ID of a paper. It can also be used to
27-
provide more information about a paper.
27+
from Semantic Scholar. Use this agent when the user asks for
28+
general paper searches and recommendations. This agent can also
29+
retrieve the Semantic Scholar ID of a paper. It can also be used to
30+
provide more information about a paper.
31+
2. Zotero_agent: This agent can be used to retrieve, display, and query
32+
papers from the Zotero library. Use this agent only when the user
33+
explicitly asks for papers from Zotero. This agent can also
34+
retrieve the Semantic Scholar ID of a paper.
35+
36+
CRITICAL RULES:
37+
1. Do not generate any content or modify worker outputs
38+
2. Route to FINISH ONLY when a worker has COMPLETELY finished their task
39+
3. For the S2_agent and zotero_agent, ensure it has both SEARCHED and DISPLAYED results before FINISH
40+
41+
Available workers: members
42+
43+
Worker descriptions:
44+
1. S2_agent: Routes to this agent ONLY for:
45+
- Initial paper searches
46+
- Getting paper recommendations
47+
- Retrieving paper IDs based on the title of a paper
48+
- Displaying search/recommendation results
49+
- Query over papers
50+
2. Zotero_agent: Routes to this agent ONLY for:
51+
- Paper/journals searches from Zotero library
52+
- Displaying search results
53+
- Retrieving paper IDs based on the title of a paper
54+
- Query over papers
2855
29-
Here are some instructions for the workers:
30-
1. Call the S2 agent for general paper searches and recommendations.
31-
2. The S2 agent has access to tools for querying and displaying papers.
32-
3. If the user wants suggestions for papers and you don’t have
33-
a Semantic Scholar ID for it but do have the title from
34-
the last displayed results, use the S2 agent to retrieve the
35-
Semantic Scholar ID of the paper. Then, use the S2 agent again to display
36-
recommendations for the paper.
37-
4. You can call the S2 agent to get more information about a paper based
38-
on the context of the conversation.
39-
5. Respond with FINISH when all tasks are completed.
56+
Respond with FINISH when and ONLY when:
57+
1. A worker has COMPLETELY finished their task (including display)
58+
2. The results have been displayed to the user using display_results
59+
3. No further action is needed

0 commit comments

Comments
 (0)