Skip to content

Multi-Agent Collaboration Notebook #1859

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
11 changes: 11 additions & 0 deletions authors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

# You can optionally customize how your information shows up cookbook.openai.com over here.
# If your information is not present here, it will be pulled from your GitHub profile.
rajpathak-openai:
name: "Raj Pathak"
website: "https://www.linkedin.com/in/rajpathakopenai/"
avatar: "https://avatars.githubusercontent.com/u/208723614?s=400&u=c852eed3be082f7fbd402b5a45e9b89a0bfed1b8&v=4"

chelseahu-openai:
name: "Chelsea Hu"
website: "https://www.linkedin.com/in/chelsea-tsaiszuhu/"
avatar: "https://avatars.githubusercontent.com/u/196863678?v=4"

prashantmital-openai:
name: "Prashant Mital"
website: "https://www.linkedin.com/in/pmital/"
Expand Down Expand Up @@ -336,3 +346,4 @@ tompakeman-oai:
name: "Tom Pakeman"
website: "https://www.linkedin.com/in/tom-pakeman/"
avatar: "https://avatars.githubusercontent.com/u/204937754"

74 changes: 74 additions & 0 deletions examples/agents_sdk/multi-agent-portfolio-collaboration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# Installer logs
distutils.log
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Jupyter Notebook checkpoints
.ipynb_checkpoints

# pyenv
.python-version

# mypy
.mypy_cache/
.dmypy.json

# Pyre type checker
.pyre/

# VS Code
.vscode/

# Mac OS
.DS_Store

# Output and log directories
outputs/
logs/

# Project-specific logs and outputs
*.log
*.jsonl

# Secret keys and environment variables
.env
.env.*
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# This file marks the agents directory as a Python package.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from dataclasses import dataclass
from investment_agents.fundamental import build_fundamental_agent
from investment_agents.macro import build_macro_agent
from investment_agents.quant import build_quant_agent
from investment_agents.editor import build_editor_agent, build_memo_edit_tool
from investment_agents.pm import build_head_pm_agent, SpecialistRequestInput
import asyncio

@dataclass
class InvestmentAgentsBundle:
head_pm: object
fundamental: object
macro: object
quant: object


def build_investment_agents() -> InvestmentAgentsBundle:
fundamental = build_fundamental_agent()
macro = build_macro_agent()
quant = build_quant_agent()
editor = build_editor_agent()
memo_edit_tool = build_memo_edit_tool(editor)
head_pm = build_head_pm_agent(fundamental, macro, quant, memo_edit_tool)
return InvestmentAgentsBundle(
head_pm=head_pm,
fundamental=fundamental,
macro=macro,
quant=quant,
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from agents import Agent, ModelSettings, function_tool, Runner, RunContextWrapper
from tools import write_markdown, read_file, list_output_files
from utils import load_prompt, DISCLAIMER
from pydantic import BaseModel
import json

default_model = "gpt-4.1"

class MemoEditorInput(BaseModel):
fundamental: str
macro: str
quant: str
pm: str
files: list[str]

def build_editor_agent():
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
editor_prompt = load_prompt("editor_base.md")
return Agent(
name="Memo Editor Agent",
instructions=(editor_prompt + DISCLAIMER + tool_retry_instructions),
tools=[write_markdown, read_file, list_output_files],
model=default_model,
model_settings=ModelSettings(temperature=0),
)

def build_memo_edit_tool(editor):
@function_tool(
name_override="memo_editor",
description_override="Stitch analysis sections into a Markdown memo and save it. This is the ONLY way to generate and save the final investment report. All memos must be finalized through this tool.",
)
async def memo_edit_tool(ctx: RunContextWrapper, input: MemoEditorInput) -> str:
result = await Runner.run(
starting_agent=editor,
input=json.dumps(input.model_dump()),
context=ctx.context,
max_turns=40,
)
return result.final_output
return memo_edit_tool
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from agents import Agent, WebSearchTool, ModelSettings
from utils import load_prompt, DISCLAIMER, repo_path
from pathlib import Path

default_model = "gpt-4.1"
default_search_context = "medium"
RECENT_DAYS = 15

def build_fundamental_agent():
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
fundamental_prompt = load_prompt("fundamental_base.md", RECENT_DAYS=RECENT_DAYS)
# Set up the Yahoo Finance MCP server
from agents.mcp import MCPServerStdio
server_path = str(repo_path("mcp/yahoo_finance_server.py"))
yahoo_mcp_server = MCPServerStdio(
params={"command": "python", "args": [server_path]},
client_session_timeout_seconds=300,
cache_tools_list=True,
)

return Agent(
name="Fundamental Analysis Agent",
instructions=(fundamental_prompt + DISCLAIMER + tool_retry_instructions),
mcp_servers=[yahoo_mcp_server],
tools=[WebSearchTool(search_context_size=default_search_context)],
model=default_model,
model_settings=ModelSettings(parallel_tool_calls=True, temperature=0),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from agents import Agent, WebSearchTool, ModelSettings
from tools import get_fred_series
from utils import load_prompt, DISCLAIMER

default_model = "gpt-4.1"
default_search_context = "medium"
RECENT_DAYS = 45

def build_macro_agent():
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
macro_prompt = load_prompt("macro_base.md", RECENT_DAYS=RECENT_DAYS)
return Agent(
name="Macro Analysis Agent",
instructions=(macro_prompt + DISCLAIMER + tool_retry_instructions),
tools=[WebSearchTool(search_context_size=default_search_context), get_fred_series],
model=default_model,
model_settings=ModelSettings(parallel_tool_calls=True, temperature=0),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from agents import Agent, ModelSettings, function_tool, Runner
from utils import load_prompt, DISCLAIMER
from dataclasses import dataclass
from pydantic import BaseModel
import json
import asyncio


class SpecialistRequestInput(BaseModel):
section: str # e.g., 'fundamental', 'macro', 'quant', or 'pm'
user_question: str
guidance: str

# Core async functions for each specialist
async def specialist_analysis_func(agent, input: SpecialistRequestInput):
result = await Runner.run(
starting_agent=agent,
input=json.dumps(input.model_dump()),
max_turns=75,
)
return result.final_output

async def run_all_specialists_parallel(
fundamental, macro, quant,
fundamental_input: SpecialistRequestInput,
macro_input: SpecialistRequestInput,
quant_input: SpecialistRequestInput
):
results = await asyncio.gather(
specialist_analysis_func(fundamental, fundamental_input),
specialist_analysis_func(macro, macro_input),
specialist_analysis_func(quant, quant_input)
)
return {
"fundamental": results[0],
"macro": results[1],
"quant": results[2]
}

def build_head_pm_agent(fundamental, macro, quant, memo_edit_tool):
def make_agent_tool(agent, name, description):
@function_tool(name_override=name, description_override=description)
async def agent_tool(input: SpecialistRequestInput):
return await specialist_analysis_func(agent, input)
return agent_tool
fundamental_tool = make_agent_tool(fundamental, "fundamental_analysis", "Generate the Fundamental Analysis section.")
macro_tool = make_agent_tool(macro, "macro_analysis", "Generate the Macro Environment section.")
quant_tool = make_agent_tool(quant, "quantitative_analysis", "Generate the Quantitative Analysis section.")

@function_tool(name_override="run_all_specialists_parallel", description_override="Run all three specialist analyses (fundamental, macro, quant) in parallel and return their results as a dict.")
async def run_all_specialists_tool(fundamental_input: SpecialistRequestInput, macro_input: SpecialistRequestInput, quant_input: SpecialistRequestInput):
return await run_all_specialists_parallel(
fundamental, macro, quant,
fundamental_input, macro_input, quant_input
)

return Agent(
name="Head Portfolio Manager Agent",
instructions=(
load_prompt("pm_base.md") + DISCLAIMER
),
model="gpt-4.1",
#Reasoning model
#model="o4-mini",
tools=[fundamental_tool, macro_tool, quant_tool, memo_edit_tool, run_all_specialists_tool],
# Settings for a reasoning model
#model_settings=ModelSettings(parallel_tool_calls=True, reasoning={"summary": "auto", "effort": "high"}, tool_choice="auto")
model_settings=ModelSettings(parallel_tool_calls=True, tool_choice="auto", temperature=0)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from agents import Agent, ModelSettings
from tools import run_code_interpreter, get_fred_series, read_file, list_output_files
from utils import load_prompt, DISCLAIMER, repo_path
from pathlib import Path

default_model = "gpt-4.1"

def build_quant_agent():
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
quant_prompt = load_prompt("quant_base.md")
# Set up the Yahoo Finance MCP server
from agents.mcp import MCPServerStdio
server_path = str(repo_path("mcp/yahoo_finance_server.py"))
yahoo_mcp_server = MCPServerStdio(
params={"command": "python", "args": [server_path]},
client_session_timeout_seconds=300,
cache_tools_list=True,
)

return Agent(
name="Quantitative Analysis Agent",
instructions=(quant_prompt + DISCLAIMER + tool_retry_instructions),
mcp_servers=[yahoo_mcp_server],
tools=[run_code_interpreter, get_fred_series, read_file, list_output_files],
model=default_model,
model_settings=ModelSettings(parallel_tool_calls=True, temperature=0),
)
Loading