Skip to content

Commit c142c66

Browse files
Multi-Agent Collaboration Notebook (#1859)
Co-authored-by: Copilot <[email protected]>
1 parent 3fde156 commit c142c66

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2402
-0
lines changed

authors.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
# You can optionally customize how your information shows up cookbook.openai.com over here.
44
# If your information is not present here, it will be pulled from your GitHub profile.
5+
rajpathak-openai:
6+
name: "Raj Pathak"
7+
website: "https://www.linkedin.com/in/rajpathakopenai/"
8+
avatar: "https://avatars.githubusercontent.com/u/208723614?s=400&u=c852eed3be082f7fbd402b5a45e9b89a0bfed1b8&v=4"
9+
10+
chelseahu-openai:
11+
name: "Chelsea Hu"
12+
website: "https://www.linkedin.com/in/chelsea-tsaiszuhu/"
13+
avatar: "https://avatars.githubusercontent.com/u/196863678?v=4"
14+
515
prashantmital-openai:
616
name: "Prashant Mital"
717
website: "https://www.linkedin.com/in/pmital/"
@@ -336,3 +346,4 @@ tompakeman-oai:
336346
name: "Tom Pakeman"
337347
website: "https://www.linkedin.com/in/tom-pakeman/"
338348
avatar: "https://avatars.githubusercontent.com/u/204937754"
349+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
27+
# Installer logs
28+
distutils.log
29+
pip-log.txt
30+
pip-delete-this-directory.txt
31+
32+
# Unit test / coverage reports
33+
htmlcov/
34+
.tox/
35+
.nox/
36+
.coverage
37+
.coverage.*
38+
.cache
39+
nosetests.xml
40+
coverage.xml
41+
*.cover
42+
.hypothesis/
43+
.pytest_cache/
44+
45+
# Jupyter Notebook checkpoints
46+
.ipynb_checkpoints
47+
48+
# pyenv
49+
.python-version
50+
51+
# mypy
52+
.mypy_cache/
53+
.dmypy.json
54+
55+
# Pyre type checker
56+
.pyre/
57+
58+
# VS Code
59+
.vscode/
60+
61+
# Mac OS
62+
.DS_Store
63+
64+
# Output and log directories
65+
outputs/
66+
logs/
67+
68+
# Project-specific logs and outputs
69+
*.log
70+
*.jsonl
71+
72+
# Secret keys and environment variables
73+
.env
74+
.env.*
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file marks the agents directory as a Python package.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from dataclasses import dataclass
2+
from investment_agents.fundamental import build_fundamental_agent
3+
from investment_agents.macro import build_macro_agent
4+
from investment_agents.quant import build_quant_agent
5+
from investment_agents.editor import build_editor_agent, build_memo_edit_tool
6+
from investment_agents.pm import build_head_pm_agent, SpecialistRequestInput
7+
import asyncio
8+
9+
@dataclass
10+
class InvestmentAgentsBundle:
11+
head_pm: object
12+
fundamental: object
13+
macro: object
14+
quant: object
15+
16+
17+
def build_investment_agents() -> InvestmentAgentsBundle:
18+
fundamental = build_fundamental_agent()
19+
macro = build_macro_agent()
20+
quant = build_quant_agent()
21+
editor = build_editor_agent()
22+
memo_edit_tool = build_memo_edit_tool(editor)
23+
head_pm = build_head_pm_agent(fundamental, macro, quant, memo_edit_tool)
24+
return InvestmentAgentsBundle(
25+
head_pm=head_pm,
26+
fundamental=fundamental,
27+
macro=macro,
28+
quant=quant,
29+
)
30+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from agents import Agent, ModelSettings, function_tool, Runner, RunContextWrapper
2+
from tools import write_markdown, read_file, list_output_files
3+
from utils import load_prompt, DISCLAIMER
4+
from pydantic import BaseModel
5+
import json
6+
7+
default_model = "gpt-4.1"
8+
9+
class MemoEditorInput(BaseModel):
10+
fundamental: str
11+
macro: str
12+
quant: str
13+
pm: str
14+
files: list[str]
15+
16+
def build_editor_agent():
17+
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
18+
editor_prompt = load_prompt("editor_base.md")
19+
return Agent(
20+
name="Memo Editor Agent",
21+
instructions=(editor_prompt + DISCLAIMER + tool_retry_instructions),
22+
tools=[write_markdown, read_file, list_output_files],
23+
model=default_model,
24+
model_settings=ModelSettings(temperature=0),
25+
)
26+
27+
def build_memo_edit_tool(editor):
28+
@function_tool(
29+
name_override="memo_editor",
30+
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.",
31+
)
32+
async def memo_edit_tool(ctx: RunContextWrapper, input: MemoEditorInput) -> str:
33+
result = await Runner.run(
34+
starting_agent=editor,
35+
input=json.dumps(input.model_dump()),
36+
context=ctx.context,
37+
max_turns=40,
38+
)
39+
return result.final_output
40+
return memo_edit_tool
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from agents import Agent, WebSearchTool, ModelSettings
2+
from utils import load_prompt, DISCLAIMER, repo_path
3+
from pathlib import Path
4+
5+
default_model = "gpt-4.1"
6+
default_search_context = "medium"
7+
RECENT_DAYS = 15
8+
9+
def build_fundamental_agent():
10+
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
11+
fundamental_prompt = load_prompt("fundamental_base.md", RECENT_DAYS=RECENT_DAYS)
12+
# Set up the Yahoo Finance MCP server
13+
from agents.mcp import MCPServerStdio
14+
server_path = str(repo_path("mcp/yahoo_finance_server.py"))
15+
yahoo_mcp_server = MCPServerStdio(
16+
params={"command": "python", "args": [server_path]},
17+
client_session_timeout_seconds=300,
18+
cache_tools_list=True,
19+
)
20+
21+
return Agent(
22+
name="Fundamental Analysis Agent",
23+
instructions=(fundamental_prompt + DISCLAIMER + tool_retry_instructions),
24+
mcp_servers=[yahoo_mcp_server],
25+
tools=[WebSearchTool(search_context_size=default_search_context)],
26+
model=default_model,
27+
model_settings=ModelSettings(parallel_tool_calls=True, temperature=0),
28+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from agents import Agent, WebSearchTool, ModelSettings
2+
from tools import get_fred_series
3+
from utils import load_prompt, DISCLAIMER
4+
5+
default_model = "gpt-4.1"
6+
default_search_context = "medium"
7+
RECENT_DAYS = 45
8+
9+
def build_macro_agent():
10+
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
11+
macro_prompt = load_prompt("macro_base.md", RECENT_DAYS=RECENT_DAYS)
12+
return Agent(
13+
name="Macro Analysis Agent",
14+
instructions=(macro_prompt + DISCLAIMER + tool_retry_instructions),
15+
tools=[WebSearchTool(search_context_size=default_search_context), get_fred_series],
16+
model=default_model,
17+
model_settings=ModelSettings(parallel_tool_calls=True, temperature=0),
18+
)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from agents import Agent, ModelSettings, function_tool, Runner
2+
from utils import load_prompt, DISCLAIMER
3+
from dataclasses import dataclass
4+
from pydantic import BaseModel
5+
import json
6+
import asyncio
7+
8+
9+
class SpecialistRequestInput(BaseModel):
10+
section: str # e.g., 'fundamental', 'macro', 'quant', or 'pm'
11+
user_question: str
12+
guidance: str
13+
14+
# Core async functions for each specialist
15+
async def specialist_analysis_func(agent, input: SpecialistRequestInput):
16+
result = await Runner.run(
17+
starting_agent=agent,
18+
input=json.dumps(input.model_dump()),
19+
max_turns=75,
20+
)
21+
return result.final_output
22+
23+
async def run_all_specialists_parallel(
24+
fundamental, macro, quant,
25+
fundamental_input: SpecialistRequestInput,
26+
macro_input: SpecialistRequestInput,
27+
quant_input: SpecialistRequestInput
28+
):
29+
results = await asyncio.gather(
30+
specialist_analysis_func(fundamental, fundamental_input),
31+
specialist_analysis_func(macro, macro_input),
32+
specialist_analysis_func(quant, quant_input)
33+
)
34+
return {
35+
"fundamental": results[0],
36+
"macro": results[1],
37+
"quant": results[2]
38+
}
39+
40+
def build_head_pm_agent(fundamental, macro, quant, memo_edit_tool):
41+
def make_agent_tool(agent, name, description):
42+
@function_tool(name_override=name, description_override=description)
43+
async def agent_tool(input: SpecialistRequestInput):
44+
return await specialist_analysis_func(agent, input)
45+
return agent_tool
46+
fundamental_tool = make_agent_tool(fundamental, "fundamental_analysis", "Generate the Fundamental Analysis section.")
47+
macro_tool = make_agent_tool(macro, "macro_analysis", "Generate the Macro Environment section.")
48+
quant_tool = make_agent_tool(quant, "quantitative_analysis", "Generate the Quantitative Analysis section.")
49+
50+
@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.")
51+
async def run_all_specialists_tool(fundamental_input: SpecialistRequestInput, macro_input: SpecialistRequestInput, quant_input: SpecialistRequestInput):
52+
return await run_all_specialists_parallel(
53+
fundamental, macro, quant,
54+
fundamental_input, macro_input, quant_input
55+
)
56+
57+
return Agent(
58+
name="Head Portfolio Manager Agent",
59+
instructions=(
60+
load_prompt("pm_base.md") + DISCLAIMER
61+
),
62+
model="gpt-4.1",
63+
#Reasoning model
64+
#model="o4-mini",
65+
tools=[fundamental_tool, macro_tool, quant_tool, memo_edit_tool, run_all_specialists_tool],
66+
# Settings for a reasoning model
67+
#model_settings=ModelSettings(parallel_tool_calls=True, reasoning={"summary": "auto", "effort": "high"}, tool_choice="auto")
68+
model_settings=ModelSettings(parallel_tool_calls=True, tool_choice="auto", temperature=0)
69+
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from agents import Agent, ModelSettings
2+
from tools import run_code_interpreter, get_fred_series, read_file, list_output_files
3+
from utils import load_prompt, DISCLAIMER, repo_path
4+
from pathlib import Path
5+
6+
default_model = "gpt-4.1"
7+
8+
def build_quant_agent():
9+
tool_retry_instructions = load_prompt("tool_retry_prompt.md")
10+
quant_prompt = load_prompt("quant_base.md")
11+
# Set up the Yahoo Finance MCP server
12+
from agents.mcp import MCPServerStdio
13+
server_path = str(repo_path("mcp/yahoo_finance_server.py"))
14+
yahoo_mcp_server = MCPServerStdio(
15+
params={"command": "python", "args": [server_path]},
16+
client_session_timeout_seconds=300,
17+
cache_tools_list=True,
18+
)
19+
20+
return Agent(
21+
name="Quantitative Analysis Agent",
22+
instructions=(quant_prompt + DISCLAIMER + tool_retry_instructions),
23+
mcp_servers=[yahoo_mcp_server],
24+
tools=[run_code_interpreter, get_fred_series, read_file, list_output_files],
25+
model=default_model,
26+
model_settings=ModelSettings(parallel_tool_calls=True, temperature=0),
27+
)

0 commit comments

Comments
 (0)