Skip to content

Commit e548eec

Browse files
Merge pull request #38 from VectorInstitute/f/add_new_agent
Add Mulit-agent Implementation with Web Search Tool
2 parents 2ff3837 + e7e76cc commit e548eec

File tree

1 file changed

+202
-0
lines changed

1 file changed

+202
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
"""Example code for planner-worker agent collaboration with multiple tools."""
2+
3+
import asyncio
4+
import contextlib
5+
import signal
6+
import sys
7+
8+
import agents
9+
import gradio as gr
10+
from dotenv import load_dotenv
11+
from gradio.components.chatbot import ChatMessage
12+
from openai import AsyncOpenAI
13+
14+
from src.utils import (
15+
AsyncWeaviateKnowledgeBase,
16+
Configs,
17+
get_weaviate_async_client,
18+
oai_agent_stream_to_gradio_messages,
19+
set_up_logging,
20+
setup_langfuse_tracer,
21+
)
22+
from src.utils.langfuse.shared_client import langfuse_client
23+
from src.utils.tools.gemini_grounding import (
24+
GeminiGroundingWithGoogleSearch,
25+
ModelSettings,
26+
)
27+
28+
29+
load_dotenv(verbose=True)
30+
31+
set_up_logging()
32+
33+
AGENT_LLM_NAMES = {
34+
"worker": "gemini-2.5-flash", # less expensive,
35+
"planner": "gemini-2.5-pro", # more expensive, better at reasoning and planning
36+
}
37+
38+
configs = Configs.from_env_var()
39+
async_weaviate_client = get_weaviate_async_client(
40+
http_host=configs.weaviate_http_host,
41+
http_port=configs.weaviate_http_port,
42+
http_secure=configs.weaviate_http_secure,
43+
grpc_host=configs.weaviate_grpc_host,
44+
grpc_port=configs.weaviate_grpc_port,
45+
grpc_secure=configs.weaviate_grpc_secure,
46+
api_key=configs.weaviate_api_key,
47+
)
48+
async_openai_client = AsyncOpenAI()
49+
async_knowledgebase = AsyncWeaviateKnowledgeBase(
50+
async_weaviate_client,
51+
collection_name="enwiki_20250520",
52+
)
53+
54+
gemini_grounding_tool = GeminiGroundingWithGoogleSearch(
55+
model_settings=ModelSettings(model=AGENT_LLM_NAMES["worker"])
56+
)
57+
58+
59+
async def _cleanup_clients() -> None:
60+
"""Close async clients."""
61+
await async_weaviate_client.close()
62+
await async_openai_client.close()
63+
64+
65+
def _handle_sigint(signum: int, frame: object) -> None:
66+
"""Handle SIGINT signal to gracefully shutdown."""
67+
with contextlib.suppress(Exception):
68+
asyncio.get_event_loop().run_until_complete(_cleanup_clients())
69+
sys.exit(0)
70+
71+
72+
# Worker Agent: handles long context efficiently
73+
kb_agent = agents.Agent(
74+
name="KnowledgeBaseAgent",
75+
instructions="""
76+
You are an agent specialized in searching a knowledge base.
77+
You will receive a single search query as input.
78+
Use the 'search_knowledgebase' tool to perform a search, then return a
79+
JSON object with:
80+
- 'summary': a concise synthesis of the retrieved information in your own words
81+
- 'sources': a list of citations with {type: "kb", title: "...", section: "..."}
82+
- 'no_results': true/false
83+
84+
If the tool returns no matches, set "no_results": true and keep "sources" empty.
85+
Do NOT make up information. Do NOT return raw search results or long quotes.
86+
""",
87+
tools=[
88+
agents.function_tool(async_knowledgebase.search_knowledgebase),
89+
],
90+
# a faster, smaller model for quick searches
91+
model=agents.OpenAIChatCompletionsModel(
92+
model=AGENT_LLM_NAMES["worker"], openai_client=async_openai_client
93+
),
94+
)
95+
96+
# Main Agent: more expensive and slower, but better at complex planning
97+
main_agent = agents.Agent(
98+
name="MainAgent",
99+
instructions="""
100+
You are a deep research agent and your goal is to conduct in-depth, multi-turn
101+
research by breaking down complex queries, using the provided tools, and
102+
synthesizing the information into a comprehensive report.
103+
104+
You have access to the following tools:
105+
1. 'search_knowledgebase' - use this tool to search for information in a
106+
knowledge base. The knowledge base reflects a subset of Wikipedia as
107+
of May 2025.
108+
2. 'get_web_search_grounded_response' - use this tool for current events,
109+
news, fact-checking or when the information in the knowledge base is
110+
not sufficient to answer the question.
111+
112+
Both tools will not return raw search results or the sources themselves.
113+
Instead, they will return a concise summary of the key findings, along
114+
with the sources used to generate the summary.
115+
116+
For best performance, divide complex queries into simpler sub-queries
117+
Before calling either tool, always explain your reasoning for doing so.
118+
119+
Note that the 'get_web_search_grounded_response' tool will expand the query
120+
into multiple search queries and execute them. It will also return the
121+
queries it executed. Do not repeat them.
122+
123+
**Routing Guidelines:**
124+
- When answering a question, you should first try to use the 'search_knowledgebase'
125+
tool, unless the question requires recent information after May 2025 or
126+
has explicit recency cues.
127+
- If either tool returns insufficient information for a given query, try
128+
reformulating or using the other tool. You can call either tool multiple
129+
times to get the information you need to answer the user's question.
130+
131+
**Guidelines for synthesis**
132+
- After collecting results, write the final answer from your own synthesis.
133+
- Add a "Sources" section listing unique sources, formatted as:
134+
[1] Publisher - URL
135+
[2] Wikipedia: <Page Title> (Section: <section>)
136+
Order by first mention in your text. Every factual sentence in your final
137+
response must map to at least one source.
138+
- If web and knowledge base disagree, surface the disagreement and prefer sources
139+
with newer publication dates.
140+
- Do not invent URLs or sources.
141+
- If both tools fail, say so and suggest 2–3 refined queries.
142+
143+
Be sure to mention the sources in your response, including the URL if available,
144+
and do not make up information.
145+
""",
146+
# Allow the planner agent to invoke the worker agent.
147+
# The long context provided to the worker agent is hidden from the main agent.
148+
tools=[
149+
kb_agent.as_tool(
150+
tool_name="search_knowledgebase",
151+
tool_description=(
152+
"Search the knowledge base for a query and return a concise summary "
153+
"of the key findings, along with the sources used to generate "
154+
"the summary"
155+
),
156+
),
157+
agents.function_tool(gemini_grounding_tool.get_web_search_grounded_response),
158+
],
159+
# a larger, more capable model for planning and reasoning over summaries
160+
model=agents.OpenAIChatCompletionsModel(
161+
model=AGENT_LLM_NAMES["planner"], openai_client=async_openai_client
162+
),
163+
)
164+
165+
166+
async def _main(question: str, gr_messages: list[ChatMessage]):
167+
setup_langfuse_tracer()
168+
169+
# Use the main agent as the entry point- not the worker agent.
170+
with langfuse_client.start_as_current_span(name="Agents-SDK-Trace") as span:
171+
span.update(input=question)
172+
173+
result_stream = agents.Runner.run_streamed(main_agent, input=question)
174+
async for _item in result_stream.stream_events():
175+
gr_messages += oai_agent_stream_to_gradio_messages(_item)
176+
if len(gr_messages) > 0:
177+
yield gr_messages
178+
179+
span.update(output=result_stream.final_output)
180+
181+
182+
demo = gr.ChatInterface(
183+
_main,
184+
title="2.3 Multi-Agent with Multiple Search Tools",
185+
type="messages",
186+
examples=[
187+
"At which university did the SVP Software Engineering"
188+
" at Apple (as of June 2025) earn their engineering degree?",
189+
"How does the annual growth in the 50th-percentile income "
190+
"in the US compare with that in Canada?",
191+
],
192+
)
193+
194+
if __name__ == "__main__":
195+
async_openai_client = AsyncOpenAI()
196+
197+
signal.signal(signal.SIGINT, _handle_sigint)
198+
199+
try:
200+
demo.launch(share=True)
201+
finally:
202+
asyncio.run(_cleanup_clients())

0 commit comments

Comments
 (0)