Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- ARCHITECTURE.md +346 -0
- README.md +45 -8
- app.py +117 -0
- clarifier_agent.py +43 -0
- deep_research.py +116 -0
- email_agent.py +48 -0
- evaluator_agent.py +49 -0
- planner_agent.py +50 -0
- requirements.txt +9 -0
- research_manager.py +248 -0
- search_agent.py +17 -0
- writer_agent.py +30 -0
ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deep Research System - Architecture Documentation
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
This is an agentic research system built using OpenAI Agents SDK that performs deep research on user queries with quality evaluation and automatic improvement.
|
| 5 |
+
|
| 6 |
+
## Agentic Framework: OpenAI Agents SDK
|
| 7 |
+
|
| 8 |
+
The system uses **OpenAI Agents SDK** which provides:
|
| 9 |
+
- **Agent**: An AI agent with specific instructions and capabilities
|
| 10 |
+
- **Runner**: Executes agents and manages their execution
|
| 11 |
+
- **Tools**: Agents can be converted to tools for use by other agents
|
| 12 |
+
- **Structured Outputs**: Using Pydantic models for type-safe outputs
|
| 13 |
+
- **Tracing**: Built-in tracing for debugging and monitoring
|
| 14 |
+
|
| 15 |
+
## System Architecture
|
| 16 |
+
|
| 17 |
+
```
|
| 18 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 19 |
+
│ Gradio UI Layer │
|
| 20 |
+
│ (deep_research.py) - User Interface │
|
| 21 |
+
└────────────────────────────┬────────────────────────────────────┘
|
| 22 |
+
│
|
| 23 |
+
▼
|
| 24 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 25 |
+
│ Research Manager │
|
| 26 |
+
│ (research_manager.py) │
|
| 27 |
+
│ Orchestrates the entire research workflow │
|
| 28 |
+
└────────────────────────────┬────────────────────────────────────┘
|
| 29 |
+
│
|
| 30 |
+
┌─────────────────────┼─────────────────────┐
|
| 31 |
+
│ │ │
|
| 32 |
+
▼ ▼ ▼
|
| 33 |
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
| 34 |
+
│ Clarifier │ │ Planner │ │ Search │
|
| 35 |
+
│ Agent │ │ Agent │ │ Agent │
|
| 36 |
+
└──────────────┘ └──────────────┘ └──────────────┘
|
| 37 |
+
│ │ │
|
| 38 |
+
│ ▼ │
|
| 39 |
+
│ ┌──────────────┐ │
|
| 40 |
+
│ │ Writer │ │
|
| 41 |
+
│ │ Agent │ │
|
| 42 |
+
│ └──────────────┘ │
|
| 43 |
+
│ │ │
|
| 44 |
+
│ ▼ │
|
| 45 |
+
│ ┌──────────────┐ │
|
| 46 |
+
│ │ Evaluator │ │
|
| 47 |
+
│ │ Agent │ │
|
| 48 |
+
│ └──────────────┘ │
|
| 49 |
+
│ │ │
|
| 50 |
+
│ ▼ │
|
| 51 |
+
│ ┌──────────────┐ │
|
| 52 |
+
│ │ Email │ │
|
| 53 |
+
│ │ Agent │ │
|
| 54 |
+
│ └──────────────┘ │
|
| 55 |
+
│ │ │
|
| 56 |
+
└─────────────────────┴─────────────────────┘
|
| 57 |
+
│
|
| 58 |
+
▼
|
| 59 |
+
┌─────────────────┐
|
| 60 |
+
│ Final Report │
|
| 61 |
+
│ (Markdown) │
|
| 62 |
+
└─────────────────┘
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
## Agent Details
|
| 66 |
+
|
| 67 |
+
### 1. Clarifier Agent (`clarifier_agent.py`)
|
| 68 |
+
**Purpose**: Generates 3 clarifying questions based on user query
|
| 69 |
+
|
| 70 |
+
**Input**: User's research query
|
| 71 |
+
**Output**: `Clarification` (Pydantic model with 3 questions)
|
| 72 |
+
|
| 73 |
+
**Example**:
|
| 74 |
+
- Query: "Latest AI Agent frameworks"
|
| 75 |
+
- Output: ["What time period are you interested in?", "Are you looking for commercial or open-source?", "What specific use cases?"]
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
### 2. Planner Agent (`planner_agent.py`)
|
| 80 |
+
**Purpose**: Creates a search plan based on query + clarification answers
|
| 81 |
+
|
| 82 |
+
**Input**:
|
| 83 |
+
- Original query
|
| 84 |
+
- 3 question-answer pairs (structured as `ResearchContext`)
|
| 85 |
+
|
| 86 |
+
**Output**: `WebSearchPlan` (Pydantic model with list of search items)
|
| 87 |
+
|
| 88 |
+
**Schema Used**:
|
| 89 |
+
```python
|
| 90 |
+
ResearchContext:
|
| 91 |
+
- original_query: str
|
| 92 |
+
- clarification_qa: List[QuestionAnswerPair]
|
| 93 |
+
- question: str
|
| 94 |
+
- answer: str
|
| 95 |
+
|
| 96 |
+
WebSearchPlan:
|
| 97 |
+
- searches: List[WebSearchItem]
|
| 98 |
+
- query: str (search term)
|
| 99 |
+
- reason: str (why this search is important)
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
**Example**:
|
| 103 |
+
- Query: "Latest AI Agent frameworks in 2025"
|
| 104 |
+
- Answers: ["2025", "Both", "Enterprise automation"]
|
| 105 |
+
- Output: 2 search terms with reasons
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
### 3. Search Agent (`search_agent.py`)
|
| 110 |
+
**Purpose**: Performs web searches for each search term
|
| 111 |
+
|
| 112 |
+
**Input**: Search term + reason
|
| 113 |
+
**Output**: Search results (text)
|
| 114 |
+
|
| 115 |
+
**Execution**: Runs in parallel for all searches
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
### 4. Writer Agent (`writer_agent.py`)
|
| 120 |
+
**Purpose**: Writes comprehensive research report
|
| 121 |
+
|
| 122 |
+
**Input**:
|
| 123 |
+
- Original query
|
| 124 |
+
- Search results
|
| 125 |
+
- Clarification Q&A (optional)
|
| 126 |
+
- Evaluation feedback (optional, for regeneration)
|
| 127 |
+
|
| 128 |
+
**Output**: `ReportData` (Pydantic model)
|
| 129 |
+
```python
|
| 130 |
+
ReportData:
|
| 131 |
+
- short_summary: str
|
| 132 |
+
- markdown_report: str (full report)
|
| 133 |
+
- follow_up_questions: List[str]
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
---
|
| 137 |
+
|
| 138 |
+
### 5. Evaluator Agent (`evaluator_agent.py`)
|
| 139 |
+
**Purpose**: Evaluates report quality and provides scores
|
| 140 |
+
|
| 141 |
+
**Input**:
|
| 142 |
+
- Generated report
|
| 143 |
+
- Original query
|
| 144 |
+
- Clarification Q&A
|
| 145 |
+
|
| 146 |
+
**Output**: `ReportEvaluation` (Pydantic model)
|
| 147 |
+
```python
|
| 148 |
+
ReportEvaluation:
|
| 149 |
+
- relevance_score: CriterionScore (1-5)
|
| 150 |
+
- clarification_usage_score: CriterionScore (1-5)
|
| 151 |
+
- formatting_score: CriterionScore (1-5)
|
| 152 |
+
- completeness_score: CriterionScore (1-5)
|
| 153 |
+
- overall_quality_score: CriterionScore (1-5)
|
| 154 |
+
- average_score: float (average of all scores)
|
| 155 |
+
- feedback: str (improvement suggestions)
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
**Evaluation Criteria**:
|
| 159 |
+
1. **Relevance**: How well report addresses original query
|
| 160 |
+
2. **Use of Clarifications**: Incorporation of user's clarification answers
|
| 161 |
+
3. **Formatting & Clarity**: Structure and readability
|
| 162 |
+
4. **Completeness**: Comprehensive coverage
|
| 163 |
+
5. **Overall Quality**: General assessment
|
| 164 |
+
|
| 165 |
+
---
|
| 166 |
+
|
| 167 |
+
### 6. Email Agent (`email_agent.py`)
|
| 168 |
+
**Purpose**: Sends the final report via email
|
| 169 |
+
|
| 170 |
+
**Input**: Markdown report
|
| 171 |
+
**Output**: Email sent confirmation
|
| 172 |
+
|
| 173 |
+
**Technology**: Uses Resend API (SendGrid free tier was retired)
|
| 174 |
+
|
| 175 |
+
---
|
| 176 |
+
|
| 177 |
+
## Complete Workflow
|
| 178 |
+
|
| 179 |
+
```
|
| 180 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 181 |
+
│ WORKFLOW DIAGRAM │
|
| 182 |
+
└─────────────────────────────────────────────────────────────────┘
|
| 183 |
+
|
| 184 |
+
1. USER INPUT
|
| 185 |
+
│
|
| 186 |
+
▼
|
| 187 |
+
2. CLARIFIER AGENT
|
| 188 |
+
│ Generates 3 clarifying questions
|
| 189 |
+
│
|
| 190 |
+
▼
|
| 191 |
+
3. USER ANSWERS
|
| 192 |
+
│ User provides answers to questions
|
| 193 |
+
│
|
| 194 |
+
▼
|
| 195 |
+
4. PLANNER AGENT
|
| 196 |
+
│ Creates search plan using:
|
| 197 |
+
│ - Original query
|
| 198 |
+
│ - Question-Answer pairs (ResearchContext)
|
| 199 |
+
│
|
| 200 |
+
▼
|
| 201 |
+
5. PARALLEL SEARCHES
|
| 202 |
+
│ Search Agent runs for each search term
|
| 203 |
+
│ (Executed concurrently)
|
| 204 |
+
│
|
| 205 |
+
▼
|
| 206 |
+
6. WRITER AGENT
|
| 207 |
+
│ Generates comprehensive report using:
|
| 208 |
+
│ - Original query
|
| 209 |
+
│ - Search results
|
| 210 |
+
│ - Clarification Q&A
|
| 211 |
+
│
|
| 212 |
+
▼
|
| 213 |
+
7. EVALUATOR AGENT ⭐
|
| 214 |
+
│ Evaluates report quality:
|
| 215 |
+
│ - Scores 5 criteria (1-5 each)
|
| 216 |
+
│ - Calculates average score
|
| 217 |
+
│ - Provides feedback
|
| 218 |
+
│
|
| 219 |
+
▼
|
| 220 |
+
8. QUALITY CHECK
|
| 221 |
+
│
|
| 222 |
+
├─→ Score >= 3.0? ──YES──→ 9. EMAIL AGENT
|
| 223 |
+
│ Send report
|
| 224 |
+
│
|
| 225 |
+
└─→ Score < 3.0? ──NO──→ REGENERATION LOOP
|
| 226 |
+
│ (Max 2 attempts)
|
| 227 |
+
│
|
| 228 |
+
├─→ Attempt 1: Regenerate with feedback
|
| 229 |
+
│ └─→ Re-evaluate
|
| 230 |
+
│
|
| 231 |
+
├─→ Attempt 2: Regenerate with feedback
|
| 232 |
+
│ └─→ Re-evaluate
|
| 233 |
+
│
|
| 234 |
+
└─→ Final: Send best report (even if < 3.0)
|
| 235 |
+
└─→ 9. EMAIL AGENT
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
## Data Flow
|
| 239 |
+
|
| 240 |
+
```
|
| 241 |
+
User Query (string)
|
| 242 |
+
│
|
| 243 |
+
├─→ Clarifier Agent
|
| 244 |
+
│ └─→ Clarification (questions: List[str])
|
| 245 |
+
│
|
| 246 |
+
└─→ User Answers (List[str])
|
| 247 |
+
│
|
| 248 |
+
├─→ ResearchContext (Pydantic)
|
| 249 |
+
│ ├─→ original_query: str
|
| 250 |
+
│ └─→ clarification_qa: List[QuestionAnswerPair]
|
| 251 |
+
│
|
| 252 |
+
├─→ Planner Agent
|
| 253 |
+
│ └─→ WebSearchPlan
|
| 254 |
+
│ └─→ searches: List[WebSearchItem]
|
| 255 |
+
│
|
| 256 |
+
├─→ Search Agent (parallel)
|
| 257 |
+
│ └─→ Search Results: List[str]
|
| 258 |
+
│
|
| 259 |
+
├─→ Writer Agent
|
| 260 |
+
│ └─→ ReportData
|
| 261 |
+
│ ├─→ short_summary: str
|
| 262 |
+
│ ├─→ markdown_report: str
|
| 263 |
+
│ └─→ follow_up_questions: List[str]
|
| 264 |
+
│
|
| 265 |
+
├��→ Evaluator Agent
|
| 266 |
+
│ └─→ ReportEvaluation
|
| 267 |
+
│ ├─→ Individual scores (5 criteria)
|
| 268 |
+
│ ├─→ average_score: float
|
| 269 |
+
│ └─→ feedback: str
|
| 270 |
+
│
|
| 271 |
+
└─→ Email Agent
|
| 272 |
+
└─→ Email sent (via Resend API)
|
| 273 |
+
```
|
| 274 |
+
|
| 275 |
+
## Key Features
|
| 276 |
+
|
| 277 |
+
### 1. Structured Data with Pydantic
|
| 278 |
+
All agents use Pydantic models for type-safe, validated outputs:
|
| 279 |
+
- Ensures correct data structure
|
| 280 |
+
- Provides clear schemas
|
| 281 |
+
- Enables automatic validation
|
| 282 |
+
|
| 283 |
+
### 2. Agent Orchestration
|
| 284 |
+
The `ResearchManager` coordinates all agents:
|
| 285 |
+
- Sequential execution where needed
|
| 286 |
+
- Parallel execution for searches
|
| 287 |
+
- Conditional logic for regeneration
|
| 288 |
+
|
| 289 |
+
### 3. Quality Assurance Loop
|
| 290 |
+
- Automatic evaluation after report generation
|
| 291 |
+
- Regeneration if score < 3.0/5.0
|
| 292 |
+
- Up to 2 regeneration attempts
|
| 293 |
+
- Feedback-driven improvements
|
| 294 |
+
|
| 295 |
+
### 4. Context Preservation
|
| 296 |
+
- Original query maintained throughout
|
| 297 |
+
- Clarification Q&A passed to planner and writer
|
| 298 |
+
- Evaluation feedback used for regeneration
|
| 299 |
+
|
| 300 |
+
## Technology Stack
|
| 301 |
+
|
| 302 |
+
- **Framework**: OpenAI Agents SDK (`openai-agents`)
|
| 303 |
+
- **UI**: Gradio (Python web interface)
|
| 304 |
+
- **Email**: Resend API
|
| 305 |
+
- **Type Safety**: Pydantic models
|
| 306 |
+
- **Async**: Python asyncio for concurrent operations
|
| 307 |
+
- **Tracing**: OpenAI platform tracing for debugging
|
| 308 |
+
|
| 309 |
+
## File Structure
|
| 310 |
+
|
| 311 |
+
```
|
| 312 |
+
deep_research/
|
| 313 |
+
├── deep_research.py # Gradio UI
|
| 314 |
+
├── research_manager.py # Main orchestrator
|
| 315 |
+
├── clarifier_agent.py # Question generation
|
| 316 |
+
├── planner_agent.py # Search planning
|
| 317 |
+
├── search_agent.py # Web search execution
|
| 318 |
+
├── writer_agent.py # Report writing
|
| 319 |
+
├── evaluator_agent.py # Quality evaluation ⭐
|
| 320 |
+
├── email_agent.py # Email sending
|
| 321 |
+
└── ARCHITECTURE.md # This file
|
| 322 |
+
```
|
| 323 |
+
|
| 324 |
+
## Execution Flow Example
|
| 325 |
+
|
| 326 |
+
1. **User**: "Latest AI Agent frameworks in 2025"
|
| 327 |
+
2. **Clarifier**: Generates 3 questions
|
| 328 |
+
3. **User**: Answers questions
|
| 329 |
+
4. **Planner**: Creates 2 search terms based on query + answers
|
| 330 |
+
5. **Searches**: Run in parallel, collect results
|
| 331 |
+
6. **Writer**: Generates report incorporating clarifications
|
| 332 |
+
7. **Evaluator**: Scores report (e.g., 2.8/5.0)
|
| 333 |
+
8. **System**: Detects score < 3.0, regenerates with feedback
|
| 334 |
+
9. **Evaluator**: Re-scores (e.g., 3.5/5.0)
|
| 335 |
+
10. **System**: Approves, sends email
|
| 336 |
+
11. **User**: Receives high-quality report
|
| 337 |
+
|
| 338 |
+
## Benefits of This Architecture
|
| 339 |
+
|
| 340 |
+
1. **Modularity**: Each agent has a single responsibility
|
| 341 |
+
2. **Reusability**: Agents can be used as tools by other agents
|
| 342 |
+
3. **Quality Control**: Automatic evaluation and improvement
|
| 343 |
+
4. **User Experience**: Clarifying questions improve relevance
|
| 344 |
+
5. **Transparency**: Users see scores and regeneration attempts
|
| 345 |
+
6. **Scalability**: Easy to add new agents or modify workflow
|
| 346 |
+
|
README.md
CHANGED
|
@@ -1,12 +1,49 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji: 🚀
|
| 4 |
-
colorFrom: gray
|
| 5 |
-
colorTo: indigo
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version: 6.2.0
|
| 8 |
app_file: app.py
|
| 9 |
-
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: deep-research
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
app_file: app.py
|
| 4 |
+
sdk: gradio
|
| 5 |
+
sdk_version: 5.33.1
|
| 6 |
---
|
| 7 |
|
| 8 |
+
# Deep Research Multi-Agent System
|
| 9 |
+
|
| 10 |
+
An intelligent research system that uses multiple AI agents to perform comprehensive research on any topic. The system includes:
|
| 11 |
+
|
| 12 |
+
- **Clarifier Agent**: Generates 3 clarifying questions to better understand your research needs
|
| 13 |
+
- **Planner Agent**: Creates an optimized search plan based on your query and clarifications
|
| 14 |
+
- **Search Agent**: Performs web searches in parallel
|
| 15 |
+
- **Writer Agent**: Synthesizes information into a comprehensive report
|
| 16 |
+
- **Evaluator Agent**: Evaluates report quality and automatically improves it if needed
|
| 17 |
+
- **Email Agent**: Sends the final report via email
|
| 18 |
+
|
| 19 |
+
## Features
|
| 20 |
+
|
| 21 |
+
- 🤔 **Clarifying Questions**: Get more targeted research by answering 3 clarifying questions
|
| 22 |
+
- 🔍 **Intelligent Search Planning**: Search terms are optimized based on your clarifications
|
| 23 |
+
- 📊 **Quality Evaluation**: Reports are automatically evaluated on 5 criteria (relevance, clarity, completeness, etc.)
|
| 24 |
+
- 🔄 **Auto-Improvement**: Reports scoring below 3.0/5.0 are automatically regenerated with feedback
|
| 25 |
+
- 📧 **Email Delivery**: Receive your research report via email
|
| 26 |
+
|
| 27 |
+
## How to Use
|
| 28 |
+
|
| 29 |
+
1. Enter your research query
|
| 30 |
+
2. Answer the 3 clarifying questions that appear
|
| 31 |
+
3. Wait for the system to:
|
| 32 |
+
- Plan searches
|
| 33 |
+
- Perform web searches
|
| 34 |
+
- Write the report
|
| 35 |
+
- Evaluate quality
|
| 36 |
+
- Improve if needed
|
| 37 |
+
4. View your comprehensive research report
|
| 38 |
+
|
| 39 |
+
## Environment Variables
|
| 40 |
+
|
| 41 |
+
The following secrets need to be configured in Hugging Face Spaces:
|
| 42 |
+
|
| 43 |
+
- `OPENAI_API_KEY`: Your OpenAI API key (required)
|
| 44 |
+
- `RESEND_API_KEY`: Your Resend API key (for email functionality)
|
| 45 |
+
|
| 46 |
+
## Architecture
|
| 47 |
+
|
| 48 |
+
This system uses the OpenAI Agents SDK with a multi-agent architecture. See `ARCHITECTURE.md` for detailed documentation.
|
| 49 |
+
|
app.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
from research_manager import ResearchManager
|
| 4 |
+
from clarifier_agent import clarifier_agent
|
| 5 |
+
from agents import Runner
|
| 6 |
+
|
| 7 |
+
load_dotenv(override=True)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
async def generate_questions(query: str):
|
| 11 |
+
"""Automatically generate and display clarifying questions for the query"""
|
| 12 |
+
# Show loading message first
|
| 13 |
+
yield "Analyzing your query and generating clarifying questions...", []
|
| 14 |
+
|
| 15 |
+
# Generate questions
|
| 16 |
+
result = await Runner.run(clarifier_agent, f"User query: {query}")
|
| 17 |
+
questions = result.final_output.questions
|
| 18 |
+
|
| 19 |
+
# Format questions for display
|
| 20 |
+
questions_text = "**Please answer these clarifying questions:**\n\n"
|
| 21 |
+
for i, q in enumerate(questions, 1):
|
| 22 |
+
questions_text += f"{i}. {q}\n\n"
|
| 23 |
+
|
| 24 |
+
# Yield the formatted questions and the questions list for state
|
| 25 |
+
yield questions_text, questions
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
async def run_research(query: str, answer1: str, answer2: str, answer3: str, questions: list):
|
| 29 |
+
"""Run the research process with clarification answers"""
|
| 30 |
+
manager = ResearchManager()
|
| 31 |
+
answers = [answer1, answer2, answer3]
|
| 32 |
+
|
| 33 |
+
async for chunk in manager.run(query, questions, answers):
|
| 34 |
+
yield chunk
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
with gr.Blocks(theme=gr.themes.Default(primary_hue="sky")) as ui:
|
| 38 |
+
gr.Markdown("# Deep Research")
|
| 39 |
+
|
| 40 |
+
# State to store generated questions
|
| 41 |
+
questions_state = gr.State(value=[])
|
| 42 |
+
|
| 43 |
+
query_textbox = gr.Textbox(
|
| 44 |
+
label="What topic would you like to research?",
|
| 45 |
+
placeholder="e.g., Latest AI Agent frameworks in 2025"
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
submit_btn = gr.Button("Start Research", variant="primary")
|
| 49 |
+
|
| 50 |
+
questions_output = gr.Markdown(
|
| 51 |
+
label="Clarifying Questions",
|
| 52 |
+
value="*Enter your research query above and click 'Start Research' to generate clarifying questions.*",
|
| 53 |
+
visible=True
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
with gr.Row():
|
| 57 |
+
answer1 = gr.Textbox(label="Answer 1", placeholder="Enter your answer to question 1", visible=False)
|
| 58 |
+
answer2 = gr.Textbox(label="Answer 2", placeholder="Enter your answer to question 2", visible=False)
|
| 59 |
+
answer3 = gr.Textbox(label="Answer 3", placeholder="Enter your answer to question 3", visible=False)
|
| 60 |
+
|
| 61 |
+
continue_btn = gr.Button("Continue Research", variant="primary", visible=False)
|
| 62 |
+
report = gr.Markdown(label="Research Report")
|
| 63 |
+
|
| 64 |
+
def show_questions_ui():
|
| 65 |
+
"""Show the questions and answer fields"""
|
| 66 |
+
return (
|
| 67 |
+
gr.update(visible=True), # questions_output
|
| 68 |
+
gr.update(visible=True), # answer1
|
| 69 |
+
gr.update(visible=True), # answer2
|
| 70 |
+
gr.update(visible=True), # answer3
|
| 71 |
+
gr.update(visible=True), # continue_btn
|
| 72 |
+
gr.update(visible=False) # submit_btn
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
def hide_questions_ui():
|
| 76 |
+
"""Hide the questions and answer fields after research starts"""
|
| 77 |
+
return (
|
| 78 |
+
gr.update(visible=False), # questions_output
|
| 79 |
+
gr.update(visible=False), # answer1
|
| 80 |
+
gr.update(visible=False), # answer2
|
| 81 |
+
gr.update(visible=False), # answer3
|
| 82 |
+
gr.update(visible=False), # continue_btn
|
| 83 |
+
gr.update(visible=True) # submit_btn
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# When user submits query, automatically generate questions
|
| 87 |
+
submit_btn.click(
|
| 88 |
+
fn=generate_questions,
|
| 89 |
+
inputs=query_textbox,
|
| 90 |
+
outputs=[questions_output, questions_state]
|
| 91 |
+
).then(
|
| 92 |
+
fn=show_questions_ui,
|
| 93 |
+
outputs=[questions_output, answer1, answer2, answer3, continue_btn, submit_btn]
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
query_textbox.submit(
|
| 97 |
+
fn=generate_questions,
|
| 98 |
+
inputs=query_textbox,
|
| 99 |
+
outputs=[questions_output, questions_state]
|
| 100 |
+
).then(
|
| 101 |
+
fn=show_questions_ui,
|
| 102 |
+
outputs=[questions_output, answer1, answer2, answer3, continue_btn, submit_btn]
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
# Continue research with answers
|
| 106 |
+
continue_btn.click(
|
| 107 |
+
fn=run_research,
|
| 108 |
+
inputs=[query_textbox, answer1, answer2, answer3, questions_state],
|
| 109 |
+
outputs=report
|
| 110 |
+
).then(
|
| 111 |
+
fn=hide_questions_ui,
|
| 112 |
+
outputs=[questions_output, answer1, answer2, answer3, continue_btn, submit_btn]
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
if __name__ == "__main__":
|
| 116 |
+
ui.launch()
|
| 117 |
+
|
clarifier_agent.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from agents import Agent
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv(override=True)
|
| 9 |
+
|
| 10 |
+
openai_api_key = os.getenv('OPENAI_API_KEY')
|
| 11 |
+
|
| 12 |
+
INSTRUCTIONS = """You are a helpful research assistant.
|
| 13 |
+
Given a user's research query, you come up with 3 clarifying questions to better understand what they need.
|
| 14 |
+
|
| 15 |
+
Rules:
|
| 16 |
+
- Keep the questions short (one sentence each question)
|
| 17 |
+
- Do not answer the questions yourself
|
| 18 |
+
- Ask exactly 3 questions
|
| 19 |
+
- Every question must end with a '?'
|
| 20 |
+
- Focus on understanding: scope, depth, timeframe, specific aspects, or context
|
| 21 |
+
- Make questions relevant to the research query"""
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Clarification(BaseModel):
|
| 25 |
+
questions: List[str] = Field(
|
| 26 |
+
description="A list of exactly 3 clarifying questions",
|
| 27 |
+
min_items=3,
|
| 28 |
+
max_items=3
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
clarifier_agent = Agent(
|
| 33 |
+
name='ClarifyAgent',
|
| 34 |
+
instructions=INSTRUCTIONS,
|
| 35 |
+
model='gpt-4o-mini',
|
| 36 |
+
output_type=Clarification
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
clarify_tool = clarifier_agent.as_tool(
|
| 40 |
+
tool_name='clarify',
|
| 41 |
+
tool_description='Ask 3 clarifying questions based on the user query'
|
| 42 |
+
)
|
| 43 |
+
|
deep_research.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from dotenv import load_dotenv
|
| 3 |
+
from research_manager import ResearchManager
|
| 4 |
+
from clarifier_agent import clarifier_agent
|
| 5 |
+
from agents import Runner
|
| 6 |
+
|
| 7 |
+
load_dotenv(override=True)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
async def generate_questions(query: str):
|
| 11 |
+
"""Automatically generate and display clarifying questions for the query"""
|
| 12 |
+
# Show loading message first
|
| 13 |
+
yield "Analyzing your query and generating clarifying questions...", []
|
| 14 |
+
|
| 15 |
+
# Generate questions
|
| 16 |
+
result = await Runner.run(clarifier_agent, f"User query: {query}")
|
| 17 |
+
questions = result.final_output.questions
|
| 18 |
+
|
| 19 |
+
# Format questions for display
|
| 20 |
+
questions_text = "**Please answer these clarifying questions:**\n\n"
|
| 21 |
+
for i, q in enumerate(questions, 1):
|
| 22 |
+
questions_text += f"{i}. {q}\n\n"
|
| 23 |
+
|
| 24 |
+
# Yield the formatted questions and the questions list for state
|
| 25 |
+
yield questions_text, questions
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
async def run_research(query: str, answer1: str, answer2: str, answer3: str, questions: list):
|
| 29 |
+
"""Run the research process with clarification answers"""
|
| 30 |
+
manager = ResearchManager()
|
| 31 |
+
answers = [answer1, answer2, answer3]
|
| 32 |
+
|
| 33 |
+
async for chunk in manager.run(query, questions, answers):
|
| 34 |
+
yield chunk
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
with gr.Blocks(theme=gr.themes.Default(primary_hue="sky")) as ui:
|
| 38 |
+
gr.Markdown("# Deep Research")
|
| 39 |
+
|
| 40 |
+
# State to store generated questions
|
| 41 |
+
questions_state = gr.State(value=[])
|
| 42 |
+
|
| 43 |
+
query_textbox = gr.Textbox(
|
| 44 |
+
label="What topic would you like to research?",
|
| 45 |
+
placeholder="e.g., Latest AI Agent frameworks in 2025"
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
submit_btn = gr.Button("Start Research", variant="primary")
|
| 49 |
+
|
| 50 |
+
questions_output = gr.Markdown(
|
| 51 |
+
label="Clarifying Questions",
|
| 52 |
+
value="*Enter your research query above and click 'Start Research' to generate clarifying questions.*",
|
| 53 |
+
visible=True
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
with gr.Row():
|
| 57 |
+
answer1 = gr.Textbox(label="Answer 1", placeholder="Enter your answer to question 1", visible=False)
|
| 58 |
+
answer2 = gr.Textbox(label="Answer 2", placeholder="Enter your answer to question 2", visible=False)
|
| 59 |
+
answer3 = gr.Textbox(label="Answer 3", placeholder="Enter your answer to question 3", visible=False)
|
| 60 |
+
|
| 61 |
+
continue_btn = gr.Button("Continue Research", variant="primary", visible=False)
|
| 62 |
+
report = gr.Markdown(label="Research Report")
|
| 63 |
+
|
| 64 |
+
def show_questions_ui():
|
| 65 |
+
"""Show the questions and answer fields"""
|
| 66 |
+
return (
|
| 67 |
+
gr.update(visible=True), # questions_output
|
| 68 |
+
gr.update(visible=True), # answer1
|
| 69 |
+
gr.update(visible=True), # answer2
|
| 70 |
+
gr.update(visible=True), # answer3
|
| 71 |
+
gr.update(visible=True), # continue_btn
|
| 72 |
+
gr.update(visible=False) # submit_btn
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
def hide_questions_ui():
|
| 76 |
+
"""Hide the questions and answer fields after research starts"""
|
| 77 |
+
return (
|
| 78 |
+
gr.update(visible=False), # questions_output
|
| 79 |
+
gr.update(visible=False), # answer1
|
| 80 |
+
gr.update(visible=False), # answer2
|
| 81 |
+
gr.update(visible=False), # answer3
|
| 82 |
+
gr.update(visible=False), # continue_btn
|
| 83 |
+
gr.update(visible=True) # submit_btn
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# When user submits query, automatically generate questions
|
| 87 |
+
submit_btn.click(
|
| 88 |
+
fn=generate_questions,
|
| 89 |
+
inputs=query_textbox,
|
| 90 |
+
outputs=[questions_output, questions_state]
|
| 91 |
+
).then(
|
| 92 |
+
fn=show_questions_ui,
|
| 93 |
+
outputs=[questions_output, answer1, answer2, answer3, continue_btn, submit_btn]
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
query_textbox.submit(
|
| 97 |
+
fn=generate_questions,
|
| 98 |
+
inputs=query_textbox,
|
| 99 |
+
outputs=[questions_output, questions_state]
|
| 100 |
+
).then(
|
| 101 |
+
fn=show_questions_ui,
|
| 102 |
+
outputs=[questions_output, answer1, answer2, answer3, continue_btn, submit_btn]
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
# Continue research with answers
|
| 106 |
+
continue_btn.click(
|
| 107 |
+
fn=run_research,
|
| 108 |
+
inputs=[query_textbox, answer1, answer2, answer3, questions_state],
|
| 109 |
+
outputs=report
|
| 110 |
+
).then(
|
| 111 |
+
fn=hide_questions_ui,
|
| 112 |
+
outputs=[questions_output, answer1, answer2, answer3, continue_btn, submit_btn]
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
ui.launch(inbrowser=True)
|
| 116 |
+
|
email_agent.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Dict
|
| 3 |
+
|
| 4 |
+
import resend
|
| 5 |
+
from agents import Agent, function_tool
|
| 6 |
+
|
| 7 |
+
# Note: SendGrid free plans were retired (May 2025). Using Resend instead.
|
| 8 |
+
# Resend free tier: 3,000 emails/month
|
| 9 |
+
# Get API key from: https://resend.com/api-keys
|
| 10 |
+
# Add to .env: RESEND_API_KEY=re_xxxxx
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@function_tool
|
| 14 |
+
def send_email(subject: str, html_body: str) -> Dict[str, str]:
|
| 15 |
+
"""Send an email with the given subject and HTML body using Resend"""
|
| 16 |
+
api_key = os.environ.get('RESEND_API_KEY')
|
| 17 |
+
if not api_key:
|
| 18 |
+
return {"status": "error", "message": "RESEND_API_KEY not found in environment variables"}
|
| 19 |
+
|
| 20 |
+
resend.api_key = api_key
|
| 21 |
+
|
| 22 |
+
from_email = "onboarding@resend.dev" # Change to your verified sender
|
| 23 |
+
to_email = "anmolkumarimusician@gmail.com" # Change to your recipient
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
r = resend.Emails.send({
|
| 27 |
+
"from": from_email,
|
| 28 |
+
"to": to_email,
|
| 29 |
+
"subject": subject,
|
| 30 |
+
"html": html_body
|
| 31 |
+
})
|
| 32 |
+
print(f"Email sent successfully! Email ID: {r.get('id', 'N/A')}")
|
| 33 |
+
return {"status": "success", "id": r.get('id')}
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"Failed to send email: {str(e)}")
|
| 36 |
+
return {"status": "error", "message": f"Failed to send email: {str(e)}"}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
INSTRUCTIONS = """You are able to send a nicely formatted HTML email based on a detailed report.
|
| 40 |
+
You will be provided with a detailed report. You should use your tool to send one email, providing the
|
| 41 |
+
report converted into clean, well presented HTML with an appropriate subject line."""
|
| 42 |
+
|
| 43 |
+
email_agent = Agent(
|
| 44 |
+
name="Email agent",
|
| 45 |
+
instructions=INSTRUCTIONS,
|
| 46 |
+
tools=[send_email],
|
| 47 |
+
model="gpt-4o-mini",
|
| 48 |
+
)
|
evaluator_agent.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from agents import Agent
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
INSTRUCTIONS = """You are an expert evaluator for research reports. Your task is to evaluate a research report
|
| 6 |
+
based on the following criteria:
|
| 7 |
+
|
| 8 |
+
1. **Relevance**: How well does the report address the original query?
|
| 9 |
+
2. **Use of Clarifications**: How well does the report incorporate the clarification answers provided by the user?
|
| 10 |
+
3. **Formatting and Clarity**: Is the report well-structured, clearly formatted, and easy to read?
|
| 11 |
+
4. **Completeness**: Does the report provide comprehensive information on the topic?
|
| 12 |
+
5. **Overall Quality**: Overall assessment of the report's quality and usefulness
|
| 13 |
+
|
| 14 |
+
For each criterion, provide a score from 1 to 5 (where 1 is poor and 5 is excellent) and a brief justification.
|
| 15 |
+
Calculate an overall average score by taking the mean of all 5 criterion scores.
|
| 16 |
+
|
| 17 |
+
In your feedback, be specific about:
|
| 18 |
+
- What areas need improvement
|
| 19 |
+
- How to better incorporate the clarification answers
|
| 20 |
+
- Formatting and structure suggestions
|
| 21 |
+
- Any missing information or gaps
|
| 22 |
+
|
| 23 |
+
Be thorough but fair in your evaluation. Consider that the report should directly address the user's original query
|
| 24 |
+
and incorporate insights from their clarification answers."""
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class CriterionScore(BaseModel):
|
| 28 |
+
criterion: str = Field(description="The name of the evaluation criterion")
|
| 29 |
+
score: int = Field(description="Score from 1 to 5", ge=1, le=5)
|
| 30 |
+
justification: str = Field(description="Brief explanation for the score")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class ReportEvaluation(BaseModel):
|
| 34 |
+
relevance_score: CriterionScore = Field(description="Score for relevance to original query")
|
| 35 |
+
clarification_usage_score: CriterionScore = Field(description="Score for use of clarification answers")
|
| 36 |
+
formatting_score: CriterionScore = Field(description="Score for formatting and clarity")
|
| 37 |
+
completeness_score: CriterionScore = Field(description="Score for completeness of information")
|
| 38 |
+
overall_quality_score: CriterionScore = Field(description="Overall quality assessment")
|
| 39 |
+
average_score: float = Field(description="Average of all scores (out of 5)")
|
| 40 |
+
feedback: str = Field(description="Overall feedback and suggestions for improvement")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
evaluator_agent = Agent(
|
| 44 |
+
name="EvaluatorAgent",
|
| 45 |
+
instructions=INSTRUCTIONS,
|
| 46 |
+
model="gpt-4o-mini",
|
| 47 |
+
output_type=ReportEvaluation,
|
| 48 |
+
)
|
| 49 |
+
|
planner_agent.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from agents import Agent
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
HOW_MANY_SEARCHES = 2
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class QuestionAnswerPair(BaseModel):
|
| 9 |
+
question: str = Field(description="The clarifying question that was asked")
|
| 10 |
+
answer: str = Field(description="The user's answer to the clarifying question")
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ResearchContext(BaseModel):
|
| 14 |
+
original_query: str = Field(description="The original research query from the user")
|
| 15 |
+
clarification_qa: List[QuestionAnswerPair] = Field(
|
| 16 |
+
description="List of question-answer pairs from the clarification process",
|
| 17 |
+
min_items=3,
|
| 18 |
+
max_items=3
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class WebSearchItem(BaseModel):
|
| 23 |
+
reason: str = Field(description="Your reasoning for why this search is important to the query.")
|
| 24 |
+
query: str = Field(description="The search term to use for the web search.")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class WebSearchPlan(BaseModel):
|
| 28 |
+
searches: List[WebSearchItem] = Field(description="A list of web searches to perform to best answer the query.")
|
| 29 |
+
|
| 30 |
+
INSTRUCTIONS = f"""You are a helpful research assistant. Given a research context that includes:
|
| 31 |
+
1. The original user query
|
| 32 |
+
2. Three clarifying questions and their answers
|
| 33 |
+
|
| 34 |
+
Your task is to come up with {HOW_MANY_SEARCHES} web search terms that will best answer the user's query,
|
| 35 |
+
taking into account both the original query AND the clarification answers provided.
|
| 36 |
+
|
| 37 |
+
The clarification answers provide important context about:
|
| 38 |
+
- What specific aspects the user is interested in
|
| 39 |
+
- The scope and depth they want
|
| 40 |
+
- Timeframe or other constraints
|
| 41 |
+
- Specific focus areas
|
| 42 |
+
|
| 43 |
+
Use this information to create more targeted and relevant search terms. Output exactly {HOW_MANY_SEARCHES} search terms."""
|
| 44 |
+
|
| 45 |
+
planner_agent = Agent(
|
| 46 |
+
name="PlannerAgent",
|
| 47 |
+
instructions=INSTRUCTIONS,
|
| 48 |
+
model="gpt-4o-mini",
|
| 49 |
+
output_type=WebSearchPlan,
|
| 50 |
+
)
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=5.22.0
|
| 2 |
+
openai>=1.68.2
|
| 3 |
+
openai-agents>=0.0.15
|
| 4 |
+
python-dotenv>=1.0.1
|
| 5 |
+
resend>=1.0.0
|
| 6 |
+
pydantic>=2.0.0
|
| 7 |
+
typing-extensions>=4.0.0
|
| 8 |
+
httpx>=0.28.1
|
| 9 |
+
|
research_manager.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from agents import Runner, trace, gen_trace_id
|
| 2 |
+
from search_agent import search_agent
|
| 3 |
+
from planner_agent import planner_agent, WebSearchItem, WebSearchPlan, ResearchContext, QuestionAnswerPair
|
| 4 |
+
from writer_agent import writer_agent, ReportData
|
| 5 |
+
from email_agent import email_agent
|
| 6 |
+
from clarifier_agent import clarifier_agent
|
| 7 |
+
from evaluator_agent import evaluator_agent, ReportEvaluation
|
| 8 |
+
from typing import Optional, List
|
| 9 |
+
import asyncio
|
| 10 |
+
|
| 11 |
+
class ResearchManager:
|
| 12 |
+
|
| 13 |
+
async def run(self, query: str, clarification_questions: Optional[List[str]] = None, clarification_answers: Optional[List[str]] = None):
|
| 14 |
+
""" Run the deep research process, yielding the status updates and the final report"""
|
| 15 |
+
trace_id = gen_trace_id()
|
| 16 |
+
with trace("Research trace", trace_id=trace_id):
|
| 17 |
+
print(f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}")
|
| 18 |
+
yield f"View trace: https://platform.openai.com/traces/trace?trace_id={trace_id}"
|
| 19 |
+
|
| 20 |
+
# Step 1: Get clarifying questions and answers
|
| 21 |
+
if clarification_questions is None or clarification_answers is None:
|
| 22 |
+
yield "Generating clarifying questions..."
|
| 23 |
+
clarification_result = await Runner.run(clarifier_agent, f"User query: {query}")
|
| 24 |
+
questions = clarification_result.final_output.questions
|
| 25 |
+
questions_text = "**Please answer these clarifying questions:**\n\n"
|
| 26 |
+
for i, q in enumerate(questions, 1):
|
| 27 |
+
questions_text += f"{i}. {q}\n\n"
|
| 28 |
+
yield questions_text
|
| 29 |
+
return # Stop here to wait for user answers
|
| 30 |
+
|
| 31 |
+
# Step 2: Continue with structured research context
|
| 32 |
+
print("Starting research with clarifications...")
|
| 33 |
+
yield "Using your clarifications to refine the research...\n\n"
|
| 34 |
+
|
| 35 |
+
# Step 3: Plan searches using structured context
|
| 36 |
+
search_plan = await self.plan_searches(query, clarification_questions, clarification_answers)
|
| 37 |
+
yield "Searches planned, starting to search..."
|
| 38 |
+
|
| 39 |
+
# Step 4: Perform searches
|
| 40 |
+
search_results = await self.perform_searches(search_plan)
|
| 41 |
+
yield "Searches complete, writing report..."
|
| 42 |
+
|
| 43 |
+
# Step 5: Write report
|
| 44 |
+
report = await self.write_report(query, search_results, clarification_questions, clarification_answers)
|
| 45 |
+
yield "Report written, evaluating quality..."
|
| 46 |
+
|
| 47 |
+
# Step 6: Evaluate report
|
| 48 |
+
evaluation = await self.evaluate_report(report, query, clarification_questions, clarification_answers)
|
| 49 |
+
|
| 50 |
+
# Format detailed evaluation results
|
| 51 |
+
eval_details = self._format_evaluation(evaluation, attempt=1)
|
| 52 |
+
yield eval_details
|
| 53 |
+
|
| 54 |
+
# Step 7: If score is low, regenerate report with feedback
|
| 55 |
+
max_attempts = 2
|
| 56 |
+
attempt = 1
|
| 57 |
+
regeneration_attempted = False
|
| 58 |
+
|
| 59 |
+
while evaluation.average_score < 3.0 and attempt < max_attempts:
|
| 60 |
+
regeneration_attempted = True
|
| 61 |
+
attempt += 1
|
| 62 |
+
yield f"\n⚠️ **Report score ({evaluation.average_score:.2f}/5.0) is below threshold (3.0)**\n**Regenerating with improvements (Attempt {attempt})...**\n"
|
| 63 |
+
|
| 64 |
+
report = await self.write_report(
|
| 65 |
+
query,
|
| 66 |
+
search_results,
|
| 67 |
+
clarification_questions,
|
| 68 |
+
clarification_answers,
|
| 69 |
+
feedback=evaluation.feedback
|
| 70 |
+
)
|
| 71 |
+
evaluation = await self.evaluate_report(report, query, clarification_questions, clarification_answers)
|
| 72 |
+
|
| 73 |
+
# Format evaluation results for regenerated report
|
| 74 |
+
eval_details = self._format_evaluation(evaluation, attempt=attempt, is_regeneration=True)
|
| 75 |
+
yield eval_details
|
| 76 |
+
|
| 77 |
+
# Final status
|
| 78 |
+
if regeneration_attempted:
|
| 79 |
+
if evaluation.average_score < 3.0:
|
| 80 |
+
yield f"\n⚠️ **Final Status**: Score {evaluation.average_score:.2f}/5.0 (below threshold, max attempts reached)"
|
| 81 |
+
else:
|
| 82 |
+
yield f"\n✅ **Final Status**: Report improved! Final score: {evaluation.average_score:.2f}/5.0"
|
| 83 |
+
else:
|
| 84 |
+
yield f"\n✅ **Final Status**: Report quality approved! Score: {evaluation.average_score:.2f}/5.0"
|
| 85 |
+
|
| 86 |
+
# Step 8: Send email
|
| 87 |
+
yield "Sending email..."
|
| 88 |
+
await self.send_email(report)
|
| 89 |
+
yield "Email sent, research complete"
|
| 90 |
+
yield report.markdown_report
|
| 91 |
+
|
| 92 |
+
async def plan_searches(self, query: str, questions: List[str], answers: List[str]) -> WebSearchPlan:
|
| 93 |
+
""" Plan the searches to perform using the structured research context """
|
| 94 |
+
print("Planning searches...")
|
| 95 |
+
|
| 96 |
+
# Create structured research context with question-answer pairs
|
| 97 |
+
qa_pairs = [
|
| 98 |
+
QuestionAnswerPair(question=q, answer=a)
|
| 99 |
+
for q, a in zip(questions, answers)
|
| 100 |
+
]
|
| 101 |
+
|
| 102 |
+
research_context = ResearchContext(
|
| 103 |
+
original_query=query,
|
| 104 |
+
clarification_qa=qa_pairs
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
# Format the context for the planner agent
|
| 108 |
+
context_text = f"""Original Query: {research_context.original_query}
|
| 109 |
+
|
| 110 |
+
Clarification Questions and Answers:
|
| 111 |
+
"""
|
| 112 |
+
for i, qa in enumerate(research_context.clarification_qa, 1):
|
| 113 |
+
context_text += f"{i}. Question: {qa.question}\n Answer: {qa.answer}\n\n"
|
| 114 |
+
|
| 115 |
+
result = await Runner.run(
|
| 116 |
+
planner_agent,
|
| 117 |
+
context_text,
|
| 118 |
+
)
|
| 119 |
+
print(f"Will perform {len(result.final_output.searches)} searches")
|
| 120 |
+
return result.final_output_as(WebSearchPlan)
|
| 121 |
+
|
| 122 |
+
async def perform_searches(self, search_plan: WebSearchPlan) -> List[str]:
|
| 123 |
+
""" Perform the searches to perform for the query """
|
| 124 |
+
print("Searching...")
|
| 125 |
+
num_completed = 0
|
| 126 |
+
tasks = [asyncio.create_task(self.search(item)) for item in search_plan.searches]
|
| 127 |
+
results = []
|
| 128 |
+
for task in asyncio.as_completed(tasks):
|
| 129 |
+
result = await task
|
| 130 |
+
if result is not None:
|
| 131 |
+
results.append(result)
|
| 132 |
+
num_completed += 1
|
| 133 |
+
print(f"Searching... {num_completed}/{len(tasks)} completed")
|
| 134 |
+
print("Finished searching")
|
| 135 |
+
return results
|
| 136 |
+
|
| 137 |
+
async def search(self, item: WebSearchItem) -> Optional[str]:
|
| 138 |
+
""" Perform a search for the query """
|
| 139 |
+
input = f"Search term: {item.query}\nReason for searching: {item.reason}"
|
| 140 |
+
try:
|
| 141 |
+
result = await Runner.run(
|
| 142 |
+
search_agent,
|
| 143 |
+
input,
|
| 144 |
+
)
|
| 145 |
+
return str(result.final_output)
|
| 146 |
+
except Exception:
|
| 147 |
+
return None
|
| 148 |
+
|
| 149 |
+
async def write_report(
|
| 150 |
+
self,
|
| 151 |
+
query: str,
|
| 152 |
+
search_results: List[str],
|
| 153 |
+
clarification_questions: Optional[List[str]] = None,
|
| 154 |
+
clarification_answers: Optional[List[str]] = None,
|
| 155 |
+
feedback: Optional[str] = None
|
| 156 |
+
) -> ReportData:
|
| 157 |
+
""" Write the report for the query """
|
| 158 |
+
print("Thinking about report...")
|
| 159 |
+
|
| 160 |
+
# Build input with query, search results, and clarifications
|
| 161 |
+
input_parts = [f"Original query: {query}"]
|
| 162 |
+
input_parts.append(f"Summarized search results: {search_results}")
|
| 163 |
+
|
| 164 |
+
if clarification_questions and clarification_answers:
|
| 165 |
+
input_parts.append("\nClarification Questions and Answers:")
|
| 166 |
+
for i, (q, a) in enumerate(zip(clarification_questions, clarification_answers), 1):
|
| 167 |
+
input_parts.append(f"{i}. Question: {q}\n Answer: {a}")
|
| 168 |
+
|
| 169 |
+
if feedback:
|
| 170 |
+
input_parts.append(f"\n\nIMPORTANT: Previous evaluation feedback for improvement:\n{feedback}\n\nPlease address these issues in your report.")
|
| 171 |
+
|
| 172 |
+
input_text = "\n".join(input_parts)
|
| 173 |
+
|
| 174 |
+
result = await Runner.run(
|
| 175 |
+
writer_agent,
|
| 176 |
+
input_text,
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
print("Finished writing report")
|
| 180 |
+
return result.final_output_as(ReportData)
|
| 181 |
+
|
| 182 |
+
async def evaluate_report(
|
| 183 |
+
self,
|
| 184 |
+
report: ReportData,
|
| 185 |
+
query: str,
|
| 186 |
+
clarification_questions: Optional[List[str]],
|
| 187 |
+
clarification_answers: Optional[List[str]]
|
| 188 |
+
) -> ReportEvaluation:
|
| 189 |
+
""" Evaluate the quality of the report """
|
| 190 |
+
print("Evaluating report...")
|
| 191 |
+
|
| 192 |
+
# Build evaluation context
|
| 193 |
+
eval_context = f"""Original Query: {query}
|
| 194 |
+
|
| 195 |
+
Clarification Questions and Answers:
|
| 196 |
+
"""
|
| 197 |
+
if clarification_questions and clarification_answers:
|
| 198 |
+
for i, (q, a) in enumerate(zip(clarification_questions, clarification_answers), 1):
|
| 199 |
+
eval_context += f"{i}. Question: {q}\n Answer: {a}\n\n"
|
| 200 |
+
|
| 201 |
+
eval_context += f"""
|
| 202 |
+
Report to Evaluate:
|
| 203 |
+
{report.markdown_report}
|
| 204 |
+
|
| 205 |
+
Please evaluate this report based on:
|
| 206 |
+
1. How well it addresses the original query
|
| 207 |
+
2. How well it incorporates the clarification answers
|
| 208 |
+
3. Formatting and clarity
|
| 209 |
+
4. Completeness
|
| 210 |
+
5. Overall quality
|
| 211 |
+
"""
|
| 212 |
+
|
| 213 |
+
result = await Runner.run(
|
| 214 |
+
evaluator_agent,
|
| 215 |
+
eval_context,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
evaluation = result.final_output_as(ReportEvaluation)
|
| 219 |
+
print(f"Evaluation complete. Average score: {evaluation.average_score:.2f}/5.0")
|
| 220 |
+
return evaluation
|
| 221 |
+
|
| 222 |
+
async def send_email(self, report: ReportData) -> None:
|
| 223 |
+
print("Writing email...")
|
| 224 |
+
result = await Runner.run(
|
| 225 |
+
email_agent,
|
| 226 |
+
report.markdown_report,
|
| 227 |
+
)
|
| 228 |
+
print("Email sent")
|
| 229 |
+
return report
|
| 230 |
+
|
| 231 |
+
def _format_evaluation(self, evaluation: ReportEvaluation, attempt: int = 1, is_regeneration: bool = False) -> str:
|
| 232 |
+
"""Format evaluation results for display"""
|
| 233 |
+
header = "🔄 **Regeneration Attempt " + str(attempt) + "**\n\n" if is_regeneration else "📊 **Evaluation Results**\n\n"
|
| 234 |
+
|
| 235 |
+
details = f"""{header}
|
| 236 |
+
**Overall Score: {evaluation.average_score:.2f}/5.0**
|
| 237 |
+
|
| 238 |
+
**Detailed Scores:**
|
| 239 |
+
- **Relevance**: {evaluation.relevance_score.score}/5 - {evaluation.relevance_score.justification}
|
| 240 |
+
- **Use of Clarifications**: {evaluation.clarification_usage_score.score}/5 - {evaluation.clarification_usage_score.justification}
|
| 241 |
+
- **Formatting & Clarity**: {evaluation.formatting_score.score}/5 - {evaluation.formatting_score.justification}
|
| 242 |
+
- **Completeness**: {evaluation.completeness_score.score}/5 - {evaluation.completeness_score.justification}
|
| 243 |
+
- **Overall Quality**: {evaluation.overall_quality_score.score}/5 - {evaluation.overall_quality_score.justification}
|
| 244 |
+
|
| 245 |
+
**Feedback:**
|
| 246 |
+
{evaluation.feedback}
|
| 247 |
+
"""
|
| 248 |
+
return details
|
search_agent.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from agents import Agent, WebSearchTool, ModelSettings
|
| 2 |
+
|
| 3 |
+
INSTRUCTIONS = (
|
| 4 |
+
"You are a research assistant. Given a search term, you search the web for that term and "
|
| 5 |
+
"produce a concise summary of the results. The summary must 2-3 paragraphs and less than 300 "
|
| 6 |
+
"words. Capture the main points. Write succintly, no need to have complete sentences or good "
|
| 7 |
+
"grammar. This will be consumed by someone synthesizing a report, so its vital you capture the "
|
| 8 |
+
"essence and ignore any fluff. Do not include any additional commentary other than the summary itself."
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
search_agent = Agent(
|
| 12 |
+
name="Search agent",
|
| 13 |
+
instructions=INSTRUCTIONS,
|
| 14 |
+
tools=[WebSearchTool(search_context_size="low")],
|
| 15 |
+
model="gpt-4o-mini",
|
| 16 |
+
model_settings=ModelSettings(tool_choice="required"),
|
| 17 |
+
)
|
writer_agent.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field
|
| 2 |
+
from agents import Agent
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
INSTRUCTIONS = (
|
| 6 |
+
"You are a senior researcher tasked with writing a cohesive report for a research query. "
|
| 7 |
+
"You will be provided with the original query, and some initial research done by a research assistant.\n"
|
| 8 |
+
"You should first come up with an outline for the report that describes the structure and "
|
| 9 |
+
"flow of the report. Then, generate the report and return that as your final output.\n"
|
| 10 |
+
"The final output should be in markdown format, and it should be lengthy and detailed. Aim "
|
| 11 |
+
"for 5-10 pages of content, at least 1000 words.\n"
|
| 12 |
+
"If clarification questions and answers are provided, make sure to incorporate those insights into your report.\n"
|
| 13 |
+
"If feedback from evaluation is provided, address those specific points to improve the report."
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class ReportData(BaseModel):
|
| 18 |
+
short_summary: str = Field(description="A short 2-3 sentence summary of the findings.")
|
| 19 |
+
|
| 20 |
+
markdown_report: str = Field(description="The final report")
|
| 21 |
+
|
| 22 |
+
follow_up_questions: List[str] = Field(description="Suggested topics to research further")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
writer_agent = Agent(
|
| 26 |
+
name="WriterAgent",
|
| 27 |
+
instructions=INSTRUCTIONS,
|
| 28 |
+
model="gpt-4o-mini",
|
| 29 |
+
output_type=ReportData,
|
| 30 |
+
)
|