Joseph Pollack commited on
Commit
b234660
·
unverified ·
1 Parent(s): 310c797

adds final fixes

Browse files
Files changed (43) hide show
  1. AUDIO_INPUT_FIX.md +1 -0
  2. ERROR_FIXES_SUMMARY.md +1 -0
  3. FILE_OUTPUT_IMPLEMENTATION_PLAN.md +1 -0
  4. FILE_OUTPUT_VERIFICATION.md +1 -0
  5. FIX_SUMMARY.md +100 -0
  6. MULTIMODAL_SETTINGS_IMPLEMENTATION_PLAN.md +1 -0
  7. MULTIMODAL_SETTINGS_IMPLEMENTATION_SUMMARY.md +1 -0
  8. README.md +49 -20
  9. REPORT_WRITING_AGENTS_ANALYSIS.md +1 -0
  10. SERPER_WEBSEARCH_IMPLEMENTATION_PLAN.md +1 -0
  11. dev/__init__.py +1 -0
  12. docs/api/agents.md +1 -0
  13. docs/api/models.md +1 -0
  14. docs/api/services.md +1 -0
  15. docs/api/tools.md +1 -0
  16. docs/architecture/agents.md +1 -0
  17. docs/contributing/code-quality.md +1 -0
  18. docs/contributing/code-style.md +1 -0
  19. docs/contributing/error-handling.md +1 -0
  20. docs/contributing/implementation-patterns.md +1 -0
  21. docs/contributing/index.md +1 -0
  22. docs/contributing/prompt-engineering.md +1 -0
  23. docs/contributing/testing.md +1 -0
  24. docs/getting-started/installation.md +1 -0
  25. docs/implementation/IMPLEMENTATION_SUMMARY.md +1 -0
  26. docs/implementation/TTS_MODAL_IMPLEMENTATION.md +1 -0
  27. docs/license.md +1 -0
  28. docs/team.md +1 -0
  29. new_env.txt +1 -0
  30. src/app.py +17 -13
  31. src/middleware/state_machine.py +1 -0
  32. src/orchestrator/graph_orchestrator.py +79 -6
  33. src/services/report_file_service.py +1 -0
  34. src/tools/searchxng_web_search.py +1 -0
  35. src/tools/serper_web_search.py +1 -0
  36. src/tools/vendored/crawl_website.py +1 -0
  37. src/tools/vendored/searchxng_client.py +1 -0
  38. src/tools/vendored/serper_client.py +1 -0
  39. src/tools/vendored/web_search_core.py +1 -0
  40. src/tools/web_search_factory.py +1 -0
  41. tests/unit/middleware/test_budget_tracker_phase7.py +1 -0
  42. tests/unit/middleware/test_state_machine.py +1 -0
  43. tests/unit/middleware/test_workflow_manager.py +1 -0
AUDIO_INPUT_FIX.md CHANGED
@@ -89,3 +89,4 @@ If audio input still doesn't appear:
89
  - The `file_types` parameter ensures audio files are accepted for upload
90
 
91
 
 
 
89
  - The `file_types` parameter ensures audio files are accepted for upload
90
 
91
 
92
+
ERROR_FIXES_SUMMARY.md CHANGED
@@ -151,3 +151,4 @@ Some MCP tools use `gr.State` inputs, which Gradio warns won't update between to
151
  4. Document the tuple format handling for future reference
152
 
153
 
 
 
151
  4. Document the tuple format handling for future reference
152
 
153
 
154
+
FILE_OUTPUT_IMPLEMENTATION_PLAN.md CHANGED
@@ -236,3 +236,4 @@ Current implementation in `event_to_chat_message()` already handles this correct
236
 
237
 
238
 
 
 
236
 
237
 
238
 
239
+
FILE_OUTPUT_VERIFICATION.md CHANGED
@@ -219,3 +219,4 @@ The implementation is:
219
  No reimplementation needed. All changes are present and correct.
220
 
221
 
 
 
219
  No reimplementation needed. All changes are present and correct.
220
 
221
 
222
+
FIX_SUMMARY.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fix Summary: Research Results Not Returned to User
2
+
3
+ ## Problem
4
+ The application was returning "Research completed" instead of the actual research report content to users. Reports were being generated and saved to files, but the final result wasn't being properly extracted and returned to the Gradio interface.
5
+
6
+ ## Root Causes
7
+
8
+ 1. **Incomplete Result Extraction**: The `_execute_graph` method in `graph_orchestrator.py` only checked the last executed node (`current_node_id`) for the final result. If the graph execution broke early due to budget/time limits, or if the last node wasn't the synthesizer/writer exit node, the result wouldn't be found.
9
+
10
+ 2. **Incomplete Dict Handling**: When the synthesizer or writer nodes returned a dict with `{"message": final_report, "file": file_path}`, the code only extracted the message if the dict had a "file" key. If the dict had a "message" key but no "file" key, the message wouldn't be extracted.
11
+
12
+ 3. **No Fallback Logic**: There was no fallback to check all exit nodes for results if the current node wasn't an exit node.
13
+
14
+ ## Solution
15
+
16
+ ### Changes Made to `src/orchestrator/graph_orchestrator.py`
17
+
18
+ 1. **Enhanced Result Extraction** (lines 555-600):
19
+ - First checks if `current_node_id` is an exit node and gets its result
20
+ - If no result, prioritizes checking "synthesizer" and "writer" exit nodes
21
+ - Falls back to checking all exit nodes if still no result
22
+ - Added comprehensive logging to help debug result extraction
23
+
24
+ 2. **Improved Dict Handling** (lines 602-640):
25
+ - Now checks for "message" key first (most important)
26
+ - Extracts message from dict even if "file" key is missing
27
+ - Only uses default messages if "message" key is not present
28
+ - Added logging for result type and extraction process
29
+
30
+ 3. **Better Error Handling**:
31
+ - Logs warnings when no result is found, including all available node results
32
+ - Logs unexpected result types for debugging
33
+
34
+ ## Key Code Changes
35
+
36
+ ### Before:
37
+ ```python
38
+ final_result = context.get_node_result(current_node_id) if current_node_id else None
39
+ message: str = "Research completed"
40
+
41
+ if isinstance(final_result, str):
42
+ message = final_result
43
+ elif isinstance(final_result, dict):
44
+ if "file" in final_result:
45
+ # Only extracts message if file exists
46
+ message = final_result.get("message", "Report generated. Download available.")
47
+ ```
48
+
49
+ ### After:
50
+ ```python
51
+ # Check all exit nodes with priority
52
+ final_result = None
53
+ if current_node_id and current_node_id in self._graph.exit_nodes:
54
+ final_result = context.get_node_result(current_node_id)
55
+
56
+ if not final_result:
57
+ # Prioritize synthesizer/writer nodes
58
+ for exit_node_id in ["synthesizer", "writer"]:
59
+ if exit_node_id in self._graph.exit_nodes:
60
+ result = context.get_node_result(exit_node_id)
61
+ if result:
62
+ final_result = result
63
+ break
64
+
65
+ # Extract message from dict first
66
+ if isinstance(final_result, dict):
67
+ if "message" in final_result:
68
+ message = final_result["message"] # Extract message regardless of file
69
+ ```
70
+
71
+ ## Testing Recommendations
72
+
73
+ 1. **Test Deep Research Flow**:
74
+ - Run a query that triggers deep research mode
75
+ - Verify the full report is returned, not just "Research completed"
76
+ - Check that reports are properly displayed in the UI
77
+
78
+ 2. **Test Iterative Research Flow**:
79
+ - Run a query that triggers iterative research mode
80
+ - Verify the report is returned correctly
81
+
82
+ 3. **Test Budget/Time Limits**:
83
+ - Run queries that exceed budget or time limits
84
+ - Verify that partial results are still returned if available
85
+
86
+ 4. **Test File Saving**:
87
+ - Verify reports are saved to files
88
+ - Verify file paths are included in event data when available
89
+
90
+ ## Files Modified
91
+
92
+ - `src/orchestrator/graph_orchestrator.py`: Enhanced result extraction and message handling logic
93
+
94
+ ## Expected Behavior After Fix
95
+
96
+ - Users will see the full research report content in the chat interface
97
+ - Reports will be properly extracted from synthesizer/writer nodes
98
+ - File paths will be included in event data when reports are saved
99
+ - Better logging will help debug any future issues with result extraction
100
+
MULTIMODAL_SETTINGS_IMPLEMENTATION_PLAN.md CHANGED
@@ -381,3 +381,4 @@ result["content"] = f"{content}\n\n{file_links}"
381
  - ✅ No regressions in existing functionality
382
 
383
 
 
 
381
  - ✅ No regressions in existing functionality
382
 
383
 
384
+
MULTIMODAL_SETTINGS_IMPLEMENTATION_SUMMARY.md CHANGED
@@ -152,3 +152,4 @@
152
  5. **Error Handling**: Add better error messages for failed file operations
153
 
154
 
 
 
152
  5. **Error Handling**: Add better error messages for failed file operations
153
 
154
 
155
+
README.md CHANGED
@@ -43,6 +43,7 @@ tags:
43
  [![GitHub](https://img.shields.io/github/stars/DeepCritical/GradioDemo?style=for-the-badge&logo=github&logoColor=white&label=GitHub&labelColor=181717&color=181717)](https://github.com/DeepCritical/GradioDemo)
44
  [![Documentation](https://img.shields.io/badge/Docs-0080FF?style=for-the-badge&logo=readthedocs&logoColor=white&labelColor=0080FF&color=0080FF)](deepcritical.github.io/GradioDemo/)
45
  [![Demo](https://img.shields.io/badge/Demo-FFD21E?style=for-the-badge&logo=huggingface&logoColor=white&labelColor=FFD21E&color=FFD21E)](https://huggingface.co/spaces/DataQuests/DeepCritical)
 
46
  [![codecov](https://codecov.io/gh/DeepCritical/GradioDemo/graph/badge.svg?token=B1f05RCGpz)](https://codecov.io/gh/DeepCritical/GradioDemo)
47
  [![Join us on Discord](https://img.shields.io/discord/1109943800132010065?label=Discord&logo=discord&style=flat-square)](https://discord.gg/qdfnvSPcqP)
48
 
@@ -55,17 +56,46 @@ tags:
55
 
56
  The DETERMINATOR is a powerful generalist deep research agent system that stops at nothing until finding precise answers to complex questions. It uses iterative search-and-judge loops to comprehensively investigate any research question from any domain.
57
 
58
- **Key Features**:
59
- - **Generalist**: Handles queries from any domain (medical, technical, business, scientific, etc.)
60
- - **Automatic Medical Detection**: Automatically determines if medical knowledge sources (PubMed, ClinicalTrials.gov) are needed
61
- - **Multi-Source Search**: Web search, PubMed, ClinicalTrials.gov, Europe PMC, RAG
62
- - **Stops at Nothing**: Only stops at configured limits (budget, time, iterations), otherwise continues until finding precise answers
63
- - **Evidence Synthesis**: Comprehensive reports with proper citations
64
 
65
- **Important**: The DETERMINATOR is a research tool that synthesizes evidence. It cannot provide medical advice or answer medical questions directly.
 
 
 
 
 
 
66
 
67
- For this hackathon we're proposing a simple yet powerful Deep Research Agent that iteratively looks for the answer until it finds it using general purpose websearch and special purpose retrievers for technical retrievers.
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  > [!IMPORTANT]
71
  > **IF YOU ARE A JUDGE**
@@ -76,18 +106,15 @@ For this hackathon we're proposing a simple yet powerful Deep Research Agent tha
76
  > - 📖 **Complete README**: Check out the [full README](.github/README.md) for setup, configuration, and contribution guidelines
77
  > - 🏆 **Hackathon Submission**: Keep reading below for more information about our MCP Hackathon submission
78
 
79
- ## Deep Critical In the Medial
80
 
81
- - Social Medial Posts about Deep Critical :
82
- - 𝕏 [![X](https://x.com/marioaderman/status/1995247432444133471)]
83
- - 💼 [![LinkedIn](https://www.linkedin.com/feed/update/urn:li:activity:7400984658496081920/)]
84
- - 𝕏 [![X](https://x.com/viratzzs/status/1995258812165664942)]
85
- x profile: https://x.com/viratzzs/
86
- linkedin: https://www.linkedin.com/in/viratchauhan/
87
- hf: https://huggingface.co/ViratChauhan
88
- -
89
- -
90
- -
91
 
92
  ## Important information
93
 
@@ -102,7 +129,7 @@ hf: https://huggingface.co/ViratChauhan
102
  - [] Apply Deep Research Systems To Generate Short Form Video (up to 5 minutes)
103
  - [] Visualize Pydantic Graphs as Loading Screens in the UI
104
  - [] Improve Data Science with more Complex Graph Agents
105
- - [] Create The DETERMINATOR Deep Research Demo
106
  - [] Create Deep Critical Literal Review
107
  - [] Create Deep Critical Hypothesis Generator
108
  - [] Create PyPi Package
@@ -134,6 +161,8 @@ hf: https://huggingface.co/ViratChauhan
134
  - 𝕏 [X](https://x.com/viratzzs/)
135
  - 💼 [LinkedIn](https://www.linkedin.com/in/viratchauhan/)
136
  - 🤗 [HuggingFace](https://huggingface.co/ViratChauhan)
 
 
137
 
138
 
139
  ## Acknowledgements
 
43
  [![GitHub](https://img.shields.io/github/stars/DeepCritical/GradioDemo?style=for-the-badge&logo=github&logoColor=white&label=GitHub&labelColor=181717&color=181717)](https://github.com/DeepCritical/GradioDemo)
44
  [![Documentation](https://img.shields.io/badge/Docs-0080FF?style=for-the-badge&logo=readthedocs&logoColor=white&labelColor=0080FF&color=0080FF)](deepcritical.github.io/GradioDemo/)
45
  [![Demo](https://img.shields.io/badge/Demo-FFD21E?style=for-the-badge&logo=huggingface&logoColor=white&labelColor=FFD21E&color=FFD21E)](https://huggingface.co/spaces/DataQuests/DeepCritical)
46
+ [![YouTube](https://img.shields.io/badge/YouTube-FF0000?style=for-the-badge&logo=youtube&logoColor=white&label=Watch%20Demo&labelColor=FF0000&color=FF0000)](https://www.youtube.com/watch?v=PLACEHOLDER)
47
  [![codecov](https://codecov.io/gh/DeepCritical/GradioDemo/graph/badge.svg?token=B1f05RCGpz)](https://codecov.io/gh/DeepCritical/GradioDemo)
48
  [![Join us on Discord](https://img.shields.io/discord/1109943800132010065?label=Discord&logo=discord&style=flat-square)](https://discord.gg/qdfnvSPcqP)
49
 
 
56
 
57
  The DETERMINATOR is a powerful generalist deep research agent system that stops at nothing until finding precise answers to complex questions. It uses iterative search-and-judge loops to comprehensively investigate any research question from any domain.
58
 
 
 
 
 
 
 
59
 
60
+ > For this hackathon we're proposing a simple yet powerful Deep Research Agent that iteratively looks for the answer until it finds it using general purpose websearch and special purpose retrievers for technical retrievers.
61
+
62
+ ## Who We Are & Motivation
63
+
64
+ We're a group from the `DeepCritical` Group that met in the `hugging-science` discord.
65
+
66
+ We're enthusiastic about strongly typed and robust pythonic agentic frameworks , currently building ai-assisted multi-agent systems for research automations , like critical literature reviews , clinical data retrival , and bio informatics and computational medicine applications .
67
 
68
+ Starting from Magentic Design Patterns for agentic systems , we discovered we could get better results with iterative graphs , orchestrators and planners with magentic agentics as single tools inside iterations.
69
 
70
+ ## Do You Like This App ?
71
+
72
+ Please join us @ https://hf.co/spaces/DataQuests/DeepCritical where we will keep maintaining it !
73
+
74
+ ## The DETERMINATOR is Lightweight and POWERFUL
75
+
76
+ - very accessible (multimodal inputs , audio and text out)
77
+ - fully local embeddings
78
+ - configurable providers (local/hosted) for websearch
79
+ - all data stays local
80
+ - fully configurable models and huggingface providers with login
81
+ - easily extensible and hackable
82
+ - uses Gradio a lot (clients, mcp , third party huggingface tools)
83
+ - Modal for text-to-speech
84
+ - Braxel for statistical analysis
85
+ - Open Source Models from around the 🌐World
86
+ - 💖 made with love
87
+
88
+
89
+ ## Deep Critical In the Media
90
+
91
+ - Social Medial Posts about Deep Critical :
92
+ - 𝕏 [![X](https://x.com/marioaderman/status/1995247432444133471)]
93
+ - 💼 [![LinkedIn](https://www.linkedin.com/feed/update/urn:li:activity:7400984658496081920/)]
94
+ - 𝕏 [![X](https://x.com/viratzzs/status/1995258812165664942)]
95
+
96
+ -💼 [![LinkedIn](https://www.linkedin.com/in/ana-bossler-07304717?utm_source=share&utm_campaign=share_via&utm_content=profile&utm_medium=ios_app)
97
+ -
98
+ -
99
 
100
  > [!IMPORTANT]
101
  > **IF YOU ARE A JUDGE**
 
106
  > - 📖 **Complete README**: Check out the [full README](.github/README.md) for setup, configuration, and contribution guidelines
107
  > - 🏆 **Hackathon Submission**: Keep reading below for more information about our MCP Hackathon submission
108
 
 
109
 
110
+ **Key Features**:
111
+ - **Generalist**: Handles queries from any domain (medical, technical, business, scientific, etc.)
112
+ - **Automatic Medical Detection**: Automatically determines if medical knowledge sources (PubMed, ClinicalTrials.gov) are needed
113
+ - **Multi-Source Search**: Web search, PubMed, ClinicalTrials.gov, Europe PMC, RAG
114
+ - **Stops at Nothing**: Only stops at configured limits (budget, time, iterations), otherwise continues until finding precise answers
115
+ - **Evidence Synthesis**: Comprehensive reports with proper citations
116
+
117
+ **Important**: The DETERMINATOR is a research tool that synthesizes evidence. It cannot provide medical advice or answer medical questions directly.
 
 
118
 
119
  ## Important information
120
 
 
129
  - [] Apply Deep Research Systems To Generate Short Form Video (up to 5 minutes)
130
  - [] Visualize Pydantic Graphs as Loading Screens in the UI
131
  - [] Improve Data Science with more Complex Graph Agents
132
+ - [] Create Deep Critical Drug Reporposing / Discovery Demo
133
  - [] Create Deep Critical Literal Review
134
  - [] Create Deep Critical Hypothesis Generator
135
  - [] Create PyPi Package
 
161
  - 𝕏 [X](https://x.com/viratzzs/)
162
  - 💼 [LinkedIn](https://www.linkedin.com/in/viratchauhan/)
163
  - 🤗 [HuggingFace](https://huggingface.co/ViratChauhan)
164
+ - **Anna Bossler**
165
+ - 💼 [LinkedIn](https://www.linkedin.com/in/ana-bossler-07304717)
166
 
167
 
168
  ## Acknowledgements
REPORT_WRITING_AGENTS_ANALYSIS.md CHANGED
@@ -184,3 +184,4 @@ The infrastructure to handle file outputs in Gradio is in place, but the agents
184
 
185
 
186
 
 
 
184
 
185
 
186
 
187
+
SERPER_WEBSEARCH_IMPLEMENTATION_PLAN.md CHANGED
@@ -398,3 +398,4 @@ This plan details the implementation of SERPER-based web search by vendoring cod
398
 
399
 
400
 
 
 
398
 
399
 
400
 
401
+
dev/__init__.py CHANGED
@@ -1 +1,2 @@
1
  """Development utilities and plugins."""
 
 
1
  """Development utilities and plugins."""
2
+
docs/api/agents.md CHANGED
@@ -270,3 +270,4 @@ def create_input_parser_agent(model: Any | None = None) -> InputParserAgent
270
 
271
 
272
 
 
 
270
 
271
 
272
 
273
+
docs/api/models.md CHANGED
@@ -248,3 +248,4 @@ class BudgetStatus(BaseModel):
248
 
249
 
250
 
 
 
248
 
249
 
250
 
251
+
docs/api/services.md CHANGED
@@ -205,5 +205,6 @@ Analyzes a hypothesis using statistical methods.
205
 
206
 
207
 
 
208
 
209
 
 
205
 
206
 
207
 
208
+
209
 
210
 
docs/api/tools.md CHANGED
@@ -235,3 +235,4 @@ Searches multiple tools in parallel.
235
 
236
 
237
 
 
 
235
 
236
 
237
 
238
+
docs/architecture/agents.md CHANGED
@@ -192,3 +192,4 @@ Factory functions:
192
 
193
 
194
 
 
 
192
 
193
 
194
 
195
+
docs/contributing/code-quality.md CHANGED
@@ -81,3 +81,4 @@ async def search(self, query: str, max_results: int = 10) -> list[Evidence]:
81
 
82
 
83
 
 
 
81
 
82
 
83
 
84
+
docs/contributing/code-style.md CHANGED
@@ -61,3 +61,4 @@ result = await loop.run_in_executor(None, cpu_bound_function, args)
61
 
62
 
63
 
 
 
61
 
62
 
63
 
64
+
docs/contributing/error-handling.md CHANGED
@@ -69,3 +69,4 @@ except httpx.HTTPError as e:
69
 
70
 
71
 
 
 
69
 
70
 
71
 
72
+
docs/contributing/implementation-patterns.md CHANGED
@@ -84,3 +84,4 @@ def get_embedding_service() -> EmbeddingService:
84
 
85
 
86
 
 
 
84
 
85
 
86
 
87
+
docs/contributing/index.md CHANGED
@@ -163,3 +163,4 @@ Thank you for contributing to DeepCritical!
163
 
164
 
165
 
 
 
163
 
164
 
165
 
166
+
docs/contributing/prompt-engineering.md CHANGED
@@ -69,3 +69,4 @@ This document outlines prompt engineering guidelines and citation validation rul
69
 
70
 
71
 
 
 
69
 
70
 
71
 
72
+
docs/contributing/testing.md CHANGED
@@ -73,3 +73,4 @@ async def test_real_pubmed_search():
73
  =======
74
  >>>>>>> Stashed changes
75
 
 
 
73
  =======
74
  >>>>>>> Stashed changes
75
 
76
+
docs/getting-started/installation.md CHANGED
@@ -156,3 +156,4 @@ uv run pre-commit install
156
  =======
157
  >>>>>>> Stashed changes
158
 
 
 
156
  =======
157
  >>>>>>> Stashed changes
158
 
159
+
docs/implementation/IMPLEMENTATION_SUMMARY.md CHANGED
@@ -183,3 +183,4 @@ Located in `src/app.py` lines 667-712:
183
 
184
 
185
 
 
 
183
 
184
 
185
 
186
+
docs/implementation/TTS_MODAL_IMPLEMENTATION.md CHANGED
@@ -137,3 +137,4 @@ To test TTS:
137
 
138
 
139
 
 
 
137
 
138
 
139
 
140
+
docs/license.md CHANGED
@@ -39,3 +39,4 @@ SOFTWARE.
39
 
40
 
41
 
 
 
39
 
40
 
41
 
42
+
docs/team.md CHANGED
@@ -44,3 +44,4 @@ We welcome contributions! See the [Contributing Guide](contributing/index.md) fo
44
 
45
 
46
 
 
 
44
 
45
 
46
 
47
+
new_env.txt CHANGED
@@ -99,3 +99,4 @@ MODAL_TOKEN_SECRET=your_modal_token_secret_here
99
 
100
 
101
 
 
 
99
 
100
 
101
 
102
+
src/app.py CHANGED
@@ -949,31 +949,35 @@ def create_demo() -> gr.Blocks:
949
  ),
950
  examples=[
951
  # When additional_inputs are provided, examples must be lists of lists
952
- # Each inner list: [message, mode, hf_model, hf_provider]
953
  # Using actual model IDs and provider names from inference_models.py
954
  # Note: Provider is optional - if empty, HF will auto-select
955
  # These examples will NOT run at startup - users must click them after logging in
 
956
  [
957
- "What are the latest research findings on Alzheimer's disease treatments?",
958
- "simple",
959
- "Qwen/Qwen3-Next-80B-A3B-Thinking",
960
- "",
961
- "auto",
 
962
  True,
963
  ],
964
  [
965
- "Is metformin effective for treating cancer? Investigate mechanism of action.",
966
- "iterative",
967
- "Qwen/Qwen3-235B-A22B-Instruct-2507",
 
968
  "",
969
- "iterative",
970
  True,
971
  ],
972
  [
973
- "Create a comprehensive report on Long COVID treatments including clinical trials, mechanisms, and safety.",
 
974
  "deep",
975
- "zai-org/GLM-4.5-Air",
976
- "nebius",
977
  "deep",
978
  True,
979
  ],
 
949
  ),
950
  examples=[
951
  # When additional_inputs are provided, examples must be lists of lists
952
+ # Each inner list: [message, mode, hf_model, hf_provider, graph_mode, multimodal_enabled]
953
  # Using actual model IDs and provider names from inference_models.py
954
  # Note: Provider is optional - if empty, HF will auto-select
955
  # These examples will NOT run at startup - users must click them after logging in
956
+ # All examples require deep iterative search and information retrieval across multiple sources
957
  [
958
+ # Medical research example (only one medical example)
959
+ "Create a comprehensive report on Long COVID treatments including clinical trials, mechanisms, and safety.",
960
+ "deep",
961
+ "zai-org/GLM-4.5-Air",
962
+ "nebius",
963
+ "deep",
964
  True,
965
  ],
966
  [
967
+ # Technical/Engineering example requiring deep research
968
+ "Analyze the current state of quantum computing architectures: compare different qubit technologies, error correction methods, and scalability challenges across major platforms including IBM, Google, and IonQ.",
969
+ "deep",
970
+ "Qwen/Qwen3-Next-80B-A3B-Thinking",
971
  "",
972
+ "deep",
973
  True,
974
  ],
975
  [
976
+ # Business/Scientific example requiring iterative search
977
+ "Investigate the economic and environmental impact of renewable energy transition: analyze cost trends, grid integration challenges, policy frameworks, and market dynamics across solar, wind, and battery storage technologies, in china",
978
  "deep",
979
+ "Qwen/Qwen3-235B-A22B-Instruct-2507",
980
+ "",
981
  "deep",
982
  True,
983
  ],
src/middleware/state_machine.py CHANGED
@@ -127,3 +127,4 @@ def get_workflow_state() -> WorkflowState:
127
  logger.debug("Workflow state not found, auto-initializing")
128
  return init_workflow_state()
129
  return state
 
 
127
  logger.debug("Workflow state not found, auto-initializing")
128
  return init_workflow_state()
129
  return state
130
+
src/orchestrator/graph_orchestrator.py CHANGED
@@ -552,8 +552,57 @@ class GraphOrchestrator:
552
 
553
  current_node_id = next_nodes[0] # For now, take first next node (handle parallel later)
554
 
555
- # Final event - get result from the last executed node (which should be an exit node)
556
- final_result = context.get_node_result(current_node_id) if current_node_id else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
 
558
  # Check if final result contains file information
559
  event_data: dict[str, Any] = {"mode": self.mode, "iterations": iteration}
@@ -561,21 +610,45 @@ class GraphOrchestrator:
561
 
562
  if isinstance(final_result, str):
563
  message = final_result
 
564
  elif isinstance(final_result, dict):
565
- # If result is a dict, check for file paths
 
 
 
 
 
 
 
 
566
  if "file" in final_result:
567
  file_path = final_result["file"]
568
  if isinstance(file_path, str):
569
  event_data["file"] = file_path
570
- message = final_result.get("message", "Report generated. Download available.")
 
 
 
571
  elif "files" in final_result:
572
  files = final_result["files"]
573
  if isinstance(files, list):
574
  event_data["files"] = files
575
- message = final_result.get("message", "Report generated. Downloads available.")
 
 
576
  elif isinstance(files, str):
577
  event_data["files"] = [files]
578
- message = final_result.get("message", "Report generated. Download available.")
 
 
 
 
 
 
 
 
 
 
579
 
580
  yield AgentEvent(
581
  type="complete",
 
552
 
553
  current_node_id = next_nodes[0] # For now, take first next node (handle parallel later)
554
 
555
+ # Final event - get result from exit nodes (prioritize synthesizer/writer nodes)
556
+ # First try to get result from current node (if it's an exit node)
557
+ final_result = None
558
+ if current_node_id and current_node_id in self._graph.exit_nodes:
559
+ final_result = context.get_node_result(current_node_id)
560
+ self.logger.debug(
561
+ "Final result from current exit node",
562
+ node_id=current_node_id,
563
+ has_result=final_result is not None,
564
+ result_type=type(final_result).__name__ if final_result else None,
565
+ )
566
+
567
+ # If no result from current node, check all exit nodes for results
568
+ # Prioritize synthesizer (deep research) or writer (iterative research)
569
+ if not final_result:
570
+ exit_node_priority = ["synthesizer", "writer"]
571
+ for exit_node_id in exit_node_priority:
572
+ if exit_node_id in self._graph.exit_nodes:
573
+ result = context.get_node_result(exit_node_id)
574
+ if result:
575
+ final_result = result
576
+ current_node_id = exit_node_id
577
+ self.logger.debug(
578
+ "Final result from priority exit node",
579
+ node_id=exit_node_id,
580
+ result_type=type(final_result).__name__,
581
+ )
582
+ break
583
+
584
+ # If still no result, check all exit nodes
585
+ if not final_result:
586
+ for exit_node_id in self._graph.exit_nodes:
587
+ result = context.get_node_result(exit_node_id)
588
+ if result:
589
+ final_result = result
590
+ current_node_id = exit_node_id
591
+ self.logger.debug(
592
+ "Final result from any exit node",
593
+ node_id=exit_node_id,
594
+ result_type=type(final_result).__name__,
595
+ )
596
+ break
597
+
598
+ # Log warning if no result found
599
+ if not final_result:
600
+ self.logger.warning(
601
+ "No final result found in exit nodes",
602
+ exit_nodes=list(self._graph.exit_nodes),
603
+ visited_nodes=list(context.visited_nodes),
604
+ all_node_results=list(context.node_results.keys()),
605
+ )
606
 
607
  # Check if final result contains file information
608
  event_data: dict[str, Any] = {"mode": self.mode, "iterations": iteration}
 
610
 
611
  if isinstance(final_result, str):
612
  message = final_result
613
+ self.logger.debug("Final message extracted from string result", length=len(message))
614
  elif isinstance(final_result, dict):
615
+ # First check for message key (most important)
616
+ if "message" in final_result:
617
+ message = final_result["message"]
618
+ self.logger.debug(
619
+ "Final message extracted from dict 'message' key",
620
+ length=len(message) if isinstance(message, str) else 0,
621
+ )
622
+
623
+ # Then check for file paths
624
  if "file" in final_result:
625
  file_path = final_result["file"]
626
  if isinstance(file_path, str):
627
  event_data["file"] = file_path
628
+ # Only override message if not already set from "message" key
629
+ if "message" not in final_result:
630
+ message = "Report generated. Download available."
631
+ self.logger.debug("File path added to event data", file_path=file_path)
632
  elif "files" in final_result:
633
  files = final_result["files"]
634
  if isinstance(files, list):
635
  event_data["files"] = files
636
+ # Only override message if not already set from "message" key
637
+ if "message" not in final_result:
638
+ message = "Report generated. Downloads available."
639
  elif isinstance(files, str):
640
  event_data["files"] = [files]
641
+ # Only override message if not already set from "message" key
642
+ if "message" not in final_result:
643
+ message = "Report generated. Download available."
644
+ self.logger.debug("File paths added to event data", count=len(event_data.get("files", [])))
645
+ else:
646
+ # Log warning if result type is unexpected
647
+ self.logger.warning(
648
+ "Final result has unexpected type",
649
+ result_type=type(final_result).__name__ if final_result else None,
650
+ result_repr=str(final_result)[:200] if final_result else None,
651
+ )
652
 
653
  yield AgentEvent(
654
  type="complete",
src/services/report_file_service.py CHANGED
@@ -272,3 +272,4 @@ def get_report_file_service() -> ReportFileService:
272
  return ReportFileService()
273
 
274
  return _get_service()
 
 
272
  return ReportFileService()
273
 
274
  return _get_service()
275
+
src/tools/searchxng_web_search.py CHANGED
@@ -113,3 +113,4 @@ class SearchXNGWebSearchTool:
113
  except Exception as e:
114
  logger.error("Unexpected error in SearchXNG search", error=str(e), query=final_query)
115
  raise SearchError(f"SearchXNG search failed: {e}") from e
 
 
113
  except Exception as e:
114
  logger.error("Unexpected error in SearchXNG search", error=str(e), query=final_query)
115
  raise SearchError(f"SearchXNG search failed: {e}") from e
116
+
src/tools/serper_web_search.py CHANGED
@@ -113,3 +113,4 @@ class SerperWebSearchTool:
113
  except Exception as e:
114
  logger.error("Unexpected error in Serper search", error=str(e), query=final_query)
115
  raise SearchError(f"Serper search failed: {e}") from e
 
 
113
  except Exception as e:
114
  logger.error("Unexpected error in Serper search", error=str(e), query=final_query)
115
  raise SearchError(f"Serper search failed: {e}") from e
116
+
src/tools/vendored/crawl_website.py CHANGED
@@ -125,3 +125,4 @@ async def crawl_website(starting_url: str) -> list[ScrapeResult] | str:
125
  # Use scrape_urls to get the content for all discovered pages
126
  result = await scrape_urls(pages_to_scrape_snippets)
127
  return result
 
 
125
  # Use scrape_urls to get the content for all discovered pages
126
  result = await scrape_urls(pages_to_scrape_snippets)
127
  return result
128
+
src/tools/vendored/searchxng_client.py CHANGED
@@ -94,3 +94,4 @@ class SearchXNGClient:
94
  except Exception as e:
95
  logger.error("Unexpected error in SearchXNG search", error=str(e), query=query)
96
  raise SearchError(f"SearchXNG search failed: {e}") from e
 
 
94
  except Exception as e:
95
  logger.error("Unexpected error in SearchXNG search", error=str(e), query=query)
96
  raise SearchError(f"SearchXNG search failed: {e}") from e
97
+
src/tools/vendored/serper_client.py CHANGED
@@ -90,3 +90,4 @@ class SerperClient:
90
  except Exception as e:
91
  logger.error("Unexpected error in Serper search", error=str(e), query=query)
92
  raise SearchError(f"Serper search failed: {e}") from e
 
 
90
  except Exception as e:
91
  logger.error("Unexpected error in Serper search", error=str(e), query=query)
92
  raise SearchError(f"Serper search failed: {e}") from e
93
+
src/tools/vendored/web_search_core.py CHANGED
@@ -199,3 +199,4 @@ def is_valid_url(url: str) -> bool:
199
  if any(ext in url for ext in restricted_extensions):
200
  return False
201
  return True
 
 
199
  if any(ext in url for ext in restricted_extensions):
200
  return False
201
  return True
202
+
src/tools/web_search_factory.py CHANGED
@@ -66,3 +66,4 @@ def create_web_search_tool() -> SearchTool | None:
66
  except Exception as e:
67
  logger.error("Unexpected error creating web search tool", error=str(e), provider=provider)
68
  return None
 
 
66
  except Exception as e:
67
  logger.error("Unexpected error creating web search tool", error=str(e), provider=provider)
68
  return None
69
+
tests/unit/middleware/test_budget_tracker_phase7.py CHANGED
@@ -176,4 +176,5 @@ class TestIterationTokenTracking:
176
 
177
 
178
 
 
179
 
 
176
 
177
 
178
 
179
+
180
 
tests/unit/middleware/test_state_machine.py CHANGED
@@ -373,4 +373,5 @@ class TestContextVarIsolation:
373
 
374
 
375
 
 
376
 
 
373
 
374
 
375
 
376
+
377
 
tests/unit/middleware/test_workflow_manager.py CHANGED
@@ -303,4 +303,5 @@ class TestWorkflowManager:
303
 
304
 
305
 
 
306
 
 
303
 
304
 
305
 
306
+
307