Spaces:
Sleeping
Sleeping
Commit
·
ac640bc
1
Parent(s):
fb1de84
add research agent and use it as research manager
Browse files- src/research_agent.py +51 -0
- src/research_manager.py +13 -73
src/research_agent.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from agents import Agent, ModelSettings
|
| 2 |
+
|
| 3 |
+
from planner import planner_agent
|
| 4 |
+
from report_generator import ReportData, writer_agent
|
| 5 |
+
from web_search import search_agent
|
| 6 |
+
|
| 7 |
+
INSTRUCTIONS = (
|
| 8 |
+
"You are a senior research orchestrator tasked with answering complex user questions by calling the "
|
| 9 |
+
"appropriate tools. You have access to the following tools:\n\n"
|
| 10 |
+
"1. planner_agent(input: str) -> WebSearchPlan - Produce up to five web-search queries that, when executed, "
|
| 11 |
+
"will help address the user's request.\n"
|
| 12 |
+
"3. search_agent(input: str) -> str - Run a single web search and return a concise summary of the results.\n"
|
| 13 |
+
"4. writer_agent(input: str) -> ReportData - Synthesise the final markdown report. This **must** be the last "
|
| 14 |
+
"tool you call; treat its output as your final hand-off to the user.\n\n"
|
| 15 |
+
"Workflow guidelines:\n"
|
| 16 |
+
"> Think step-by-step and decide which tool to invoke next. \n"
|
| 17 |
+
"> After gathering clarifications (if any), call planner_agent once to devise your search strategy.\n"
|
| 18 |
+
"> Next, iterate through search_agent calls to collect evidence. You may call search_agent multiple times to "
|
| 19 |
+
"cover each planned search term.\n"
|
| 20 |
+
"> If, after reviewing the results, you believe the information is still insufficient, you may make additional "
|
| 21 |
+
"calls to planner_agent or search_agent. LIMIT yourself to a maximum of **two** extra tool calls beyond your "
|
| 22 |
+
"initial plan/execution.\n"
|
| 23 |
+
"> Once satisfied, call writer_agent **exactly once** and return its markdown_report to the user as your final "
|
| 24 |
+
"answer.\n"
|
| 25 |
+
"> Never reveal chain-of-thought or internal reasoning. Keep your responses concise and focused on using the "
|
| 26 |
+
"tools effectively.\n\n"
|
| 27 |
+
"Remember: you have at most two extra tool invocations if you are unhappy with the intermediate results. Choose "
|
| 28 |
+
"them wisely."
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
planner_tool = planner_agent.as_tool(
|
| 32 |
+
tool_name="planner_agent",
|
| 33 |
+
tool_description="Produce up to five web-search queries that, when executed, will help address the user's request.",
|
| 34 |
+
)
|
| 35 |
+
web_search_tool = search_agent.as_tool(
|
| 36 |
+
tool_name="search_agent",
|
| 37 |
+
tool_description="Run a single web search and return a concise summary of the results.",
|
| 38 |
+
)
|
| 39 |
+
writer_tool = writer_agent.as_tool(
|
| 40 |
+
tool_name="writer_agent", tool_description="Synthesise the markdown report."
|
| 41 |
+
)
|
| 42 |
+
tools = [planner_tool, web_search_tool, writer_tool]
|
| 43 |
+
|
| 44 |
+
research_agent = Agent(
|
| 45 |
+
name="Research Agent",
|
| 46 |
+
instructions=INSTRUCTIONS,
|
| 47 |
+
tools=tools,
|
| 48 |
+
model="gpt-4o-mini",
|
| 49 |
+
model_settings=ModelSettings(tool_choice="auto"),
|
| 50 |
+
output_type=ReportData,
|
| 51 |
+
)
|
src/research_manager.py
CHANGED
|
@@ -1,90 +1,30 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
|
| 3 |
from agents import Runner, gen_trace_id, trace
|
| 4 |
|
| 5 |
from clarifier import ClarifyingQuestions, clarifier_agent
|
| 6 |
-
from
|
| 7 |
-
from
|
| 8 |
-
from web_search import search_agent
|
| 9 |
|
| 10 |
|
| 11 |
class ResearchManager:
|
| 12 |
|
| 13 |
async def run(self, query: str, clarifications: str | None = None):
|
| 14 |
-
"""Run the deep
|
| 15 |
-
|
| 16 |
-
If *clarifications* are provided (the user's answers to the clarifying questions), we will use them to
|
| 17 |
-
augment the planning and reporting stages. Otherwise this behaves exactly like the previous implementation.
|
| 18 |
-
"""
|
| 19 |
trace_id = gen_trace_id()
|
| 20 |
with trace("Research trace", trace_id=trace_id):
|
| 21 |
-
print("Starting research
|
| 22 |
-
|
| 23 |
-
# Combine the original query with any clarification the user has supplied.
|
| 24 |
-
if clarifications:
|
| 25 |
-
combined_query = (
|
| 26 |
-
f"Original query: {query}\n\nUser clarifications:\n{clarifications}"
|
| 27 |
-
)
|
| 28 |
-
else:
|
| 29 |
-
combined_query = query
|
| 30 |
-
|
| 31 |
-
search_plan = await self.plan_searches(combined_query)
|
| 32 |
-
yield "Searches planned, starting to search..."
|
| 33 |
-
search_results = await self.perform_searches(search_plan)
|
| 34 |
-
yield "Searches complete, writing report..."
|
| 35 |
-
report = await self.write_report(combined_query, search_results)
|
| 36 |
-
yield report.markdown_report
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
planner_agent,
|
| 43 |
-
f"Query: {query}",
|
| 44 |
-
)
|
| 45 |
-
print(f"Will perform {len(result.final_output.searches)} searches")
|
| 46 |
-
return result.final_output_as(WebSearchPlan)
|
| 47 |
-
|
| 48 |
-
async def perform_searches(self, search_plan: WebSearchPlan) -> list[str]:
|
| 49 |
-
"""Perform the searches to perform for the query"""
|
| 50 |
-
print("Searching...")
|
| 51 |
-
num_completed = 0
|
| 52 |
-
tasks = [
|
| 53 |
-
asyncio.create_task(self.search(item)) for item in search_plan.searches
|
| 54 |
-
]
|
| 55 |
-
results = []
|
| 56 |
-
for task in asyncio.as_completed(tasks):
|
| 57 |
-
result = await task
|
| 58 |
-
if result is not None:
|
| 59 |
-
results.append(result)
|
| 60 |
-
num_completed += 1
|
| 61 |
-
print(f"Searching... {num_completed}/{len(tasks)} completed")
|
| 62 |
-
print("Finished searching")
|
| 63 |
-
return results
|
| 64 |
-
|
| 65 |
-
async def search(self, item: WebSearchItem) -> str | None:
|
| 66 |
-
"""Perform a search for the query"""
|
| 67 |
-
input = f"Search term: {item.query}\nReason for searching: {item.reason}"
|
| 68 |
-
try:
|
| 69 |
-
result = await Runner.run(
|
| 70 |
-
search_agent,
|
| 71 |
-
input,
|
| 72 |
)
|
| 73 |
-
|
| 74 |
-
except Exception:
|
| 75 |
-
return None
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
print("Thinking about report...")
|
| 80 |
-
input = f"{query}\nSummarized search results: {search_results}"
|
| 81 |
-
result = await Runner.run(
|
| 82 |
-
writer_agent,
|
| 83 |
-
input,
|
| 84 |
-
)
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
|
| 89 |
async def get_clarifying_questions(self, query: str) -> list[str]:
|
| 90 |
"""Generate clarifying questions for a given query."""
|
|
|
|
|
|
|
|
|
|
| 1 |
from agents import Runner, gen_trace_id, trace
|
| 2 |
|
| 3 |
from clarifier import ClarifyingQuestions, clarifier_agent
|
| 4 |
+
from report_generator import ReportData
|
| 5 |
+
from research_agent import research_agent
|
|
|
|
| 6 |
|
| 7 |
|
| 8 |
class ResearchManager:
|
| 9 |
|
| 10 |
async def run(self, query: str, clarifications: str | None = None):
|
| 11 |
+
"""Run the deep-research pipeline using `research_agent`."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
trace_id = gen_trace_id()
|
| 13 |
with trace("Research trace", trace_id=trace_id):
|
| 14 |
+
print("Starting research with ResearchAgent…")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
combined_query = (
|
| 17 |
+
f"Original query: {query}\n\nUser clarifications:\n{clarifications}"
|
| 18 |
+
if clarifications
|
| 19 |
+
else query
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
)
|
| 21 |
+
yield "Research in progress…"
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
result = await Runner.run(research_agent, combined_query)
|
| 24 |
+
report: ReportData = result.final_output_as(ReportData)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
yield "Report generated. Rendering markdown…"
|
| 27 |
+
yield report.markdown_report
|
| 28 |
|
| 29 |
async def get_clarifying_questions(self, query: str) -> list[str]:
|
| 30 |
"""Generate clarifying questions for a given query."""
|