Didier commited on
Commit
e47da65
·
verified ·
1 Parent(s): 08e4fb8

Uploading a few agents: web_search, arxiv_search.

Browse files
Files changed (2) hide show
  1. module_agent_arxiv.py +206 -0
  2. module_agent_web_search.py +233 -0
module_agent_arxiv.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ File: web_app/module_agent_arxiv.py
3
+ Description: an agent with a tool to search arXiv papers.
4
+ Author: Didier Guillevic
5
+ Date: 2025-10-21
6
+ """
7
+
8
+ import gradio as gr
9
+
10
+ from google.adk.agents import Agent
11
+ from google.adk.runners import Runner
12
+ from google.adk.sessions import InMemorySessionService
13
+ from google.adk.tools import google_search
14
+ from google.genai import types
15
+
16
+ import asyncio
17
+ import uuid
18
+
19
+ import pydantic
20
+ import arxiv
21
+
22
+ APP_NAME="arxiv_agent"
23
+ SESSION_ID="1234"
24
+
25
+ model = "gemini-2.5-flash"
26
+
27
+ #
28
+ # ===== tool: arXiv search =====
29
+ #
30
+ class PaperArxivInfo(pydantic.BaseModel):
31
+ paper_id: str
32
+ title: str
33
+ authors: list[str]
34
+ summary: str
35
+ pdf_url: str
36
+ published: str
37
+
38
+ def search_arxiv(query: str, max_results: int=3) -> list[PaperArxivInfo]:
39
+ """Search for scientific papers on arXiv. By default returns only top 3 results unless otherwise requested by user.
40
+
41
+ Parameters:
42
+ query: The search query.
43
+ max_results: Maximum number of results (typically between 1 and 10). Default is 3.
44
+ """
45
+ print(f"[DEBUG] Tool search_arxiv received: query={query!r}, max_results={max_results!r}")
46
+ print(f"Calling search_arxiv with query: {query} and max_results: {max_results}")
47
+
48
+ # max_results is set to 3000000000000000 by the agent when not specified, so we cap it here
49
+ if max_results > 10:
50
+ max_results = 10
51
+ print(f"max_results capped to: {max_results}")
52
+
53
+ search = arxiv.Search(
54
+ query=query,
55
+ max_results=max_results,
56
+ sort_by=arxiv.SortCriterion.Relevance
57
+ )
58
+ results = arxiv.Client().results(search)
59
+
60
+ papers = []
61
+ for result in results:
62
+ paper_info = PaperArxivInfo(
63
+ paper_id=result.get_short_id(),
64
+ title=result.title,
65
+ authors=[author.name for author in result.authors],
66
+ summary=result.summary,
67
+ pdf_url=result.pdf_url,
68
+ published=result.published.strftime("%Y-%m-%d")
69
+ )
70
+ print(f"{paper_info=}")
71
+ papers.append(paper_info)
72
+
73
+ return papers
74
+
75
+ #
76
+ # ===== agent =====
77
+ #
78
+ root_agent = Agent(
79
+ name="arxiv_search_agent",
80
+ model=model,
81
+ description=(
82
+ "Agent to answer questions with the option to call arXiv Search "
83
+ "if needed for up-to-date scientific paper information."
84
+ ),
85
+ instruction=(
86
+ "I can answer your questions from my own knowledge or by searching the "
87
+ "arXiv paper repository using the arXiv Search tool. When returning "
88
+ "results about arXiv paperr, I should provide paper titles, authors, "
89
+ "summaries, as well as links to the papers. "
90
+ "Just ask me anything!"
91
+ ),
92
+ tools=[search_arxiv]
93
+ )
94
+
95
+ #
96
+ # ==== Session and Runner =====
97
+ #
98
+ async def setup_session_and_runner(user_id: str):
99
+ session_service = InMemorySessionService()
100
+ session = await session_service.create_session(
101
+ app_name=APP_NAME,
102
+ user_id=user_id,
103
+ session_id=SESSION_ID
104
+ )
105
+ runner = Runner(
106
+ agent=root_agent,
107
+ app_name=APP_NAME,
108
+ session_service=session_service
109
+ )
110
+ return session, runner
111
+
112
+ #
113
+ # ==== Call Agent Asynchronously =====
114
+ #
115
+ async def call_agent_async(query: str, user_id: str):
116
+ content = types.Content(role='user', parts=[types.Part(text=query)])
117
+ session, runner = await setup_session_and_runner(user_id=user_id)
118
+ events = runner.run_async(
119
+ user_id=user_id,
120
+ session_id=SESSION_ID,
121
+ new_message=content
122
+ )
123
+
124
+ final_response = ""
125
+
126
+ async for event in events:
127
+ if event.is_final_response():
128
+ final_response = event.content.parts[0].text
129
+
130
+ return final_response
131
+
132
+ #
133
+ # ===== User interface Block =====
134
+ #
135
+ def agent_arxiv_search(query: str, user_id=None):
136
+ """Calls a language model agent with arXiv Search tool to answer the query.
137
+
138
+ Args:
139
+ query (str): The user query.
140
+ user_id (str, optional): The user ID for session management. If None, a new ID is generated. Defaults to None.
141
+
142
+ Returns:
143
+ the agent's response (str).
144
+ """
145
+ if user_id is None:
146
+ user_id = str(uuid.uuid4()) # Generate a unique user ID
147
+
148
+ response = asyncio.run(call_agent_async(query, user_id))
149
+ return response, user_id
150
+
151
+
152
+ with gr.Blocks() as demo:
153
+ gr.Markdown(
154
+ """
155
+ **Agent with arXiv search tool**: be patient :-) Currently looking into (async) streaming support...
156
+ """
157
+ )
158
+
159
+ with gr.Row():
160
+ input_text = gr.Textbox(
161
+ lines=2,
162
+ placeholder="Enter your query here...",
163
+ label="Query",
164
+ render=True
165
+ )
166
+
167
+ user_id = gr.State(None)
168
+
169
+ with gr.Row():
170
+ submit_button = gr.Button("Submit", variant="primary")
171
+ clear_button = gr.Button("Clear", variant="secondary")
172
+
173
+ with gr.Row():
174
+ output_text = gr.Markdown(
175
+ label="Agent Response",
176
+ render=True
177
+ )
178
+
179
+ with gr.Accordion("Examples", open=False):
180
+ examples = gr.Examples(
181
+ examples=[
182
+ ["What is the prime number factorization of 21?",], # no need got Google Search
183
+ ["Can you search for a few papers on arXiv related to privacy preserving machine learning applied to language models.",],
184
+ ["Find five papers on arXiv about graph neural networks applied to financial transactions.",],
185
+ ],
186
+ inputs=[input_text,],
187
+ cache_examples=False,
188
+ label="Click to use an example"
189
+ )
190
+
191
+ # ===== Button Actions =====
192
+ submit_button.click(
193
+ fn=agent_arxiv_search,
194
+ inputs=[input_text, user_id],
195
+ outputs=[output_text, user_id]
196
+ )
197
+ clear_button.click(
198
+ fn=lambda : ('', None),
199
+ inputs=None,
200
+ outputs=[input_text, output_text]
201
+ )
202
+
203
+
204
+ if __name__ == "__main__":
205
+ demo.launch(mcp_server=True)
206
+
module_agent_web_search.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ File: web_app/module_agent_web_search.py
3
+ Description: Gradio module for the Agent Web Search functionality.
4
+ Author: Didier Guillevic
5
+ Date: 2025-10-20
6
+ """
7
+
8
+ import gradio as gr
9
+
10
+ from google.adk.agents import Agent
11
+ from google.adk.runners import Runner
12
+ from google.adk.sessions import InMemorySessionService
13
+ from google.adk.tools import google_search
14
+ from google.genai import types
15
+
16
+ import asyncio
17
+ import uuid
18
+
19
+ APP_NAME="google_search_agent"
20
+ SESSION_ID="1234"
21
+
22
+ model = "gemini-2.5-flash"
23
+
24
+ #
25
+ # ===== agent =====
26
+ #
27
+ root_agent = Agent(
28
+ name="basic_search_agent",
29
+ model=model,
30
+ description=(
31
+ "Agent to answer questions with the option to call Google Search "
32
+ "if needed for up-to-date information."
33
+ ),
34
+ instruction=(
35
+ "I can answer your questions from my own knowledge or by searching the "
36
+ "web using Google Search. Just ask me anything!"
37
+ ),
38
+ # google_search: pre-built tool allows agent to perform Google searches.
39
+ tools=[google_search]
40
+ )
41
+
42
+ #
43
+ # ===== Session and Runner =====
44
+ #
45
+ async def setup_session_and_runner(user_id: str):
46
+ session_service = InMemorySessionService()
47
+ session = await session_service.create_session(
48
+ app_name=APP_NAME,
49
+ user_id=user_id,
50
+ session_id=SESSION_ID
51
+ )
52
+ runner = Runner(
53
+ agent=root_agent,
54
+ app_name=APP_NAME,
55
+ session_service=session_service
56
+ )
57
+ return session, runner
58
+
59
+
60
+ #
61
+ # ===== Call Agent Asynchronously =====
62
+ #
63
+ async def call_agent_async(query: str, user_id: str):
64
+ content = types.Content(role='user', parts=[types.Part(text=query)])
65
+ session, runner = await setup_session_and_runner(user_id=user_id)
66
+ events = runner.run_async(
67
+ user_id=user_id,
68
+ session_id=SESSION_ID,
69
+ new_message=content
70
+ )
71
+
72
+ final_response = ""
73
+ rendered_content = ""
74
+
75
+ async for event in events:
76
+ if event.is_final_response():
77
+ final_response = event.content.parts[0].text
78
+
79
+ # Check if the event has grounding metadata and rendered content
80
+ if (
81
+ event.grounding_metadata and
82
+ event.grounding_metadata.search_entry_point and
83
+ event.grounding_metadata.search_entry_point.rendered_content
84
+ ):
85
+ rendered_content = event.grounding_metadata.search_entry_point.rendered_content
86
+ else:
87
+ rendered_content = None
88
+
89
+ return final_response, rendered_content
90
+
91
+
92
+ #
93
+ # ===== Call Agent Asynchronously with Streaming =====
94
+ #
95
+ async def call_agent_streaming(query: str, user_id: str):
96
+ content = types.Content(role='user', parts=[types.Part(text=query)])
97
+ session, runner = await setup_session_and_runner(user_id=user_id)
98
+ events = runner.run_async(
99
+ user_id=user_id,
100
+ session_id=SESSION_ID,
101
+ new_message=content
102
+ )
103
+
104
+ accumulated_response = ""
105
+ rendered_content = None # Initialize to None
106
+
107
+ async for event in events:
108
+ # Check for intermediate text parts to stream
109
+ if event.content and event.content.parts and event.content.parts[0].text:
110
+ # Accumulate and yield the new text
111
+ new_text = event.content.parts[0].text
112
+ accumulated_response += new_text
113
+ yield accumulated_response, None, user_id # Yield the current text and empty grounding
114
+
115
+ # When the final response event is received, capture the grounding content
116
+ if event.is_final_response():
117
+ # The final response text should already be in accumulated_response from earlier yields,
118
+ # but we can ensure it's fully captured here.
119
+ # accumulated_response = event.content.parts[0].text # The final text
120
+
121
+ # Capture the rendered_content from grounding_metadata
122
+ if (
123
+ event.grounding_metadata and
124
+ event.grounding_metadata.search_entry_point and
125
+ event.grounding_metadata.search_entry_point.rendered_content
126
+ ):
127
+ rendered_content = event.grounding_metadata.search_entry_point.rendered_content
128
+
129
+ # After the final response, yield one last time with the accumulated text AND the grounding content
130
+ # This final yield updates the grounding block.
131
+ yield accumulated_response, rendered_content, user_id
132
+
133
+ # If the grounding content wasn't in the final event (e.g., if no search was performed),
134
+ # make sure to yield the final accumulated text.
135
+ if rendered_content is None:
136
+ yield accumulated_response, None, user_id
137
+
138
+
139
+ #
140
+ # ===== User interface Block =====
141
+ #
142
+ def agent_web_search(query: str, user_id=None):
143
+ """Calls a language model agent with Google Search tool to answer the query.
144
+
145
+ Args:
146
+ query (str): The user query.
147
+ user_id (str, optional): The user ID for session management. If None, a new ID is generated. Defaults to None.
148
+
149
+ Returns:
150
+ tuple: A tuple containing the agent's response (str), rendered grounding content (str or None), and user_id (str).
151
+ """
152
+ if user_id is None:
153
+ user_id = str(uuid.uuid4()) # Generate a unique user ID
154
+
155
+ response, rendered_content = asyncio.run(call_agent_async(query, user_id))
156
+ return response, rendered_content, user_id
157
+
158
+
159
+ async def agent_web_search_streaming(query: str, current_user_id: str | None):
160
+ # If the user ID state is None (first run), generate a new one
161
+ if current_user_id is None:
162
+ user_id = str(uuid.uuid4())
163
+ else:
164
+ user_id = current_user_id
165
+
166
+ # The user_id is passed as part of the yield from the generator
167
+ # but we need to ensure the Gradio state is updated initially for the generator to use the correct ID.
168
+
169
+ # Gradio handles the asynchronous generator return and streams the output to the UI.
170
+ return call_agent_streaming(query, user_id)
171
+
172
+
173
+ with gr.Blocks() as demo:
174
+ gr.Markdown(
175
+ """
176
+ **Agent with Google Search tool**: be patient :-) Currently looking into (async) streaming support...
177
+ """
178
+ )
179
+
180
+ with gr.Row():
181
+ input_text = gr.Textbox(
182
+ lines=2,
183
+ placeholder="Enter your query here...",
184
+ label="Query",
185
+ render=True
186
+ )
187
+
188
+ user_id = gr.State(None)
189
+
190
+ with gr.Row():
191
+ submit_button = gr.Button("Submit", variant="primary")
192
+ clear_button = gr.Button("Clear", variant="secondary")
193
+
194
+ with gr.Row():
195
+ output_text = gr.Markdown(
196
+ label="Agent Response",
197
+ render=True
198
+ )
199
+
200
+ with gr.Row():
201
+ grounding = gr.HTML(
202
+ label="Grounding Content",
203
+ render=True
204
+ )
205
+
206
+ with gr.Accordion("Examples", open=False):
207
+ examples = gr.Examples(
208
+ examples=[
209
+ ["What is the prime number factorization of 15?",], # no need got Google Search
210
+ ["Who won the Nobel Peace Prize in 2025?",],
211
+ ["What is the weather like tomorrow in Montreal, Canada?",],
212
+ ["What are the latest news about Graph Neural Networks?",],
213
+ ],
214
+ inputs=[input_text,],
215
+ cache_examples=False,
216
+ label="Click to use an example"
217
+ )
218
+
219
+ # ===== Button Actions =====
220
+ submit_button.click(
221
+ fn=agent_web_search,
222
+ inputs=[input_text, user_id],
223
+ outputs=[output_text, grounding, user_id]
224
+ )
225
+ clear_button.click(
226
+ fn=lambda : ('', '', None),
227
+ inputs=None,
228
+ outputs=[input_text, output_text, grounding]
229
+ )
230
+
231
+
232
+ if __name__ == "__main__":
233
+ demo.launch(mcp_server=True)