Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/code_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@ jobs:
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca
with:
# Install a specific version of uv.
version: "0.5.21"
version: "0.7.20"
enable-cache: true

- name: "Set up Python"
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55
with:
python-version-file: ".python-version"

- name: Install the project
run: uv sync --dev

- name: Install dependencies and check code
run: |
source .venv/bin/activate
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ uv run --env-file .env -m src.2_frameworks.1_react_rag.langfuse_gradio
Multi-agent examples, also via the OpenAI Agent SDK.

```bash
uv run --env-file .env -m src.2_frameworks.2_multi_agent.gradio
uv run --env-file .env \
-m src.2_frameworks.2_multi_agent.planner_worker_gradio
```

### 3. Evals
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ license = "Apache-2.0"
repository = "https://github.com/VectorInstitute/agent-bootcamp"
requires-python = ">=3.12"
dependencies = [
"aiohttp>=3.12.14",
"beautifulsoup4>=4.13.4",
"datasets>=3.6.0",
"gradio>=5.35.0",
Expand Down
2 changes: 0 additions & 2 deletions src/2_frameworks/1_react_rag/gradio.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
AsyncWeaviateKnowledgeBase,
Configs,
get_weaviate_async_client,
oai_agent_items_to_gradio_messages,
oai_agent_stream_to_gradio_messages,
pretty_print,
)


Expand Down
145 changes: 145 additions & 0 deletions src/2_frameworks/2_multi_agent/planner_worker_gradio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""Example code for planner-worker agent collaboration.

With reference to:

github.com/ComplexData-MILA/misinfo-datasets
/blob/3304e6e/misinfo_data_eval/tasks/web_search.py
"""

import asyncio
import contextlib
import logging
import signal
import sys

import agents
import gradio as gr
from dotenv import load_dotenv
from gradio.components.chatbot import ChatMessage
from openai import AsyncOpenAI

from src.prompts import REACT_INSTRUCTIONS
from src.utils import (
AsyncWeaviateKnowledgeBase,
Configs,
get_weaviate_async_client,
oai_agent_stream_to_gradio_messages,
setup_langfuse_tracer,
)
from src.utils.langfuse.shared_client import langfuse_client


load_dotenv(verbose=True)


logging.basicConfig(level=logging.INFO)


AGENT_LLM_NAMES = {
"worker": "gemini-2.5-flash", # less expensive,
"planner": "gemini-2.5-pro", # more expensive, better at reasoning and planning
}

configs = Configs.from_env_var()
async_weaviate_client = get_weaviate_async_client(
http_host=configs.weaviate_http_host,
http_port=configs.weaviate_http_port,
http_secure=configs.weaviate_http_secure,
grpc_host=configs.weaviate_grpc_host,
grpc_port=configs.weaviate_grpc_port,
grpc_secure=configs.weaviate_grpc_secure,
api_key=configs.weaviate_api_key,
)
async_openai_client = AsyncOpenAI()
async_knowledgebase = AsyncWeaviateKnowledgeBase(
async_weaviate_client,
collection_name="enwiki_20250520",
)


async def _cleanup_clients() -> None:
"""Close async clients."""
await async_weaviate_client.close()
await async_openai_client.close()


def _handle_sigint(signum: int, frame: object) -> None:
"""Handle SIGINT signal to gracefully shutdown."""
with contextlib.suppress(Exception):
asyncio.get_event_loop().run_until_complete(_cleanup_clients())
sys.exit(0)


# Worker Agent: handles long context efficiently
search_agent = agents.Agent(
name="SearchAgent",
instructions=(
"You are a search agent. You receive a single search query as input. "
"Use the WebSearchTool to perform a web search, then produce a concise "
"'search summary' of the key findings. Do NOT return raw search results."
),
tools=[
agents.function_tool(async_knowledgebase.search_knowledgebase),
],
# a faster, smaller model for quick searches
model=agents.OpenAIChatCompletionsModel(
model="gemini-2.5-flash", openai_client=async_openai_client
),
)

# Main Agent: more expensive and slower, but better at complex planning
main_agent = agents.Agent(
name="MainAgent",
instructions=REACT_INSTRUCTIONS,
# Allow the planner agent to invoke the worker agent.
# The long context provided to the worker agent is hidden from the main agent.
tools=[
search_agent.as_tool(
tool_name="search",
tool_description="Perform a web search for a query and return a concise summary.",
)
],
# a larger, more capable model for planning and reasoning over summaries
model=agents.OpenAIChatCompletionsModel(
model="gemini-2.5-pro", openai_client=async_openai_client
),
)


async def _main(question: str, gr_messages: list[ChatMessage]):
setup_langfuse_tracer()

# Use the main agent as the entry point- not the worker agent.
with langfuse_client.start_as_current_span(name="Agents-SDK-Trace") as span:
span.update(input=question)

result_stream = agents.Runner.run_streamed(main_agent, input=question)
async for _item in result_stream.stream_events():
gr_messages += oai_agent_stream_to_gradio_messages(_item)
if len(gr_messages) > 0:
yield gr_messages

span.update(output=result_stream.final_output)


demo = gr.ChatInterface(
_main,
title="2.2 Multi-Agent for Efficiency",
type="messages",
examples=[
"At which university did the SVP Software Engineering"
" at Apple (as of June 2025) earn their engineering degree?",
"How does the annual growth in the 50th-percentile income "
"in the US compare with that in Canada?",
],
)

if __name__ == "__main__":
async_openai_client = AsyncOpenAI()

signal.signal(signal.SIGINT, _handle_sigint)

try:
demo.launch(server_name="0.0.0.0")
finally:
asyncio.run(_cleanup_clients())
Loading