magicboris commited on
Commit
3647b02
·
verified ·
1 Parent(s): b1bf443

Upload 83 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. backend/.gitignore +54 -0
  2. backend/CHANGELOG.md +55 -0
  3. backend/CONTRIBUTING.md +96 -0
  4. backend/DISCLAIMER.txt +35 -0
  5. backend/README.md +406 -0
  6. backend/clients.py +249 -0
  7. backend/config.py +262 -0
  8. backend/env.example +47 -0
  9. backend/frame/__init__.py +14 -0
  10. backend/frame/clients.py +221 -0
  11. backend/frame/errands/message_code_call.txt +33 -0
  12. backend/frame/errands/message_code_processing.txt +154 -0
  13. backend/frame/errands/message_code_skill_processing.txt +136 -0
  14. backend/frame/errands/message_code_variables.txt +35 -0
  15. backend/frame/errands/message_data_processing.txt +129 -0
  16. backend/frame/errands/message_generating_routine_call.txt +33 -0
  17. backend/frame/errands/message_generating_routine_processing.txt +164 -0
  18. backend/frame/errands/message_routine_call.txt +33 -0
  19. backend/frame/errands/message_routine_processing.txt +164 -0
  20. backend/frame/errands/message_routine_variables.txt +36 -0
  21. backend/frame/errands/message_type.txt +81 -0
  22. backend/frame/errands4.py +188 -0
  23. backend/frame/harness4.py +775 -0
  24. backend/frame/prompts/udr_minimal/0.code_skill.py +40 -0
  25. backend/frame/prompts/udr_minimal/1.code_skill.py +34 -0
  26. backend/frame/prompts/udr_minimal/2.auto.txt +4 -0
  27. backend/frame/prompts/udr_minimal/3.auto.txt +11 -0
  28. backend/frame/prompts/udr_minimal_generating/0.code_skill.py +34 -0
  29. backend/frame/prompts/udr_minimal_generating/1.auto.txt +4 -0
  30. backend/frame/prompts/udr_minimal_generating/2.routine_generating.txt +15 -0
  31. backend/frame/routines.py +131 -0
  32. backend/frame/tidings.py +43 -0
  33. backend/frame/trace.py +51 -0
  34. backend/insert_license.py +70 -0
  35. backend/items.py +115 -0
  36. backend/launch_server.sh +49 -0
  37. backend/main.py +471 -0
  38. backend/requirements.txt +10 -0
  39. backend/scan_research.py +536 -0
  40. backend/scan_research_dry.py +116 -0
  41. backend/sessions.py +37 -0
  42. backend/setup.py +204 -0
  43. frontend/.gitignore +48 -0
  44. frontend/CONTRIBUTING.md +124 -0
  45. frontend/DISCLAIMER.txt +35 -0
  46. frontend/README.md +171 -0
  47. frontend/env.example +19 -0
  48. frontend/eslint.config.mjs +16 -0
  49. frontend/launch_server.sh +1 -0
  50. frontend/next.config.ts +23 -0
backend/.gitignore ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .venv/
28
+
29
+ # IDE
30
+ .idea/
31
+ .vscode/
32
+ *.swp
33
+ *.swo
34
+
35
+ # Environment variables
36
+ .env
37
+ .env.local
38
+
39
+ # Logs
40
+ *.log
41
+
42
+ # OS
43
+ .DS_Store
44
+ Thumbs.db
45
+
46
+ # user-added
47
+ instances/
48
+ tavily_api.txt
49
+ nvdev_api.txt
50
+ mock_instances/
51
+ logs/
52
+ uvicorn_main.txt
53
+
54
+ .DS_Store
backend/CHANGELOG.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+ All notable changes to the Universal Deep Research Backend (UDR-B) will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2025-07-15
9
+
10
+ ### Added
11
+
12
+ - Initial release of Universal Deep Research Backend (UDR-B)
13
+ - FastAPI-based REST API with streaming responses
14
+ - Intelligent research capabilities using LLMs and web search
15
+ - Multi-model support through compatible APIs (OpenAI, NVIDIA, local vLLM)
16
+ - Tavily API integration for web search functionality
17
+ - Session management with persistent research sessions
18
+ - Advanced FrameV4 reasoning framework
19
+ - Dry run mode for testing with mock data
20
+ - Comprehensive configuration system with environment variables
21
+ - Real-time progress updates via Server-Sent Events
22
+ - CORS support for frontend integration
23
+ - Logging and tracing capabilities
24
+ - Mock data for development and testing
25
+
26
+ ### Technical Features
27
+
28
+ - `/api/research` endpoint for comprehensive research workflow
29
+ - `/api/research2` endpoint for advanced FrameV4-based research
30
+ - Modular architecture with configurable components
31
+ - Support for multiple LLM providers and models
32
+ - Environment-based configuration management
33
+ - Setup script for easy deployment
34
+ - Comprehensive error handling and validation
35
+
36
+ ### Documentation
37
+
38
+ - Complete README with setup and usage instructions
39
+ - API endpoint documentation
40
+ - Configuration guide
41
+ - Troubleshooting section
42
+ - Disclaimer for research/demonstration use
43
+
44
+ ### Research/Demonstration Focus
45
+
46
+ - Prototype implementation for AI research concepts
47
+ - Experimental features for academic research
48
+ - Not intended for production use
49
+ - Comprehensive legal disclaimer included
50
+
51
+ ---
52
+
53
+ ## Version History
54
+
55
+ - **1.0.0**: Initial research prototype release
backend/CONTRIBUTING.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to Universal Deep Research
2
+
3
+ This code details a research and demonstration prototype. This software is not intended for production use.
4
+
5
+ ## How to Contribute
6
+
7
+ ### 1. Fork and Clone
8
+
9
+ ```bash
10
+ git clone <your-fork-url>
11
+ cd backend
12
+ ```
13
+
14
+ ### 2. Set Up Development Environment
15
+
16
+ ```bash
17
+ python3 -m venv venv
18
+ source venv/bin/activate # On Windows: venv\Scripts\activate
19
+ pip install -r requirements.txt
20
+ ```
21
+
22
+ ### 3. Make Changes
23
+
24
+ - Follow existing code style and patterns
25
+ - Add tests for new functionality
26
+ - Update documentation as needed
27
+ - Ensure all TODO comments are addressed or documented
28
+
29
+ ### 4. Testing
30
+
31
+ - Test API endpoints manually
32
+ - Verify configuration changes work correctly
33
+
34
+ ### 5. Submit Pull Request
35
+
36
+ - Create a descriptive PR title
37
+ - Include clear description of changes
38
+ - Ensure code follows research/demonstration focus
39
+
40
+ ## Code Standards
41
+
42
+ - **Python**: Follow PEP 8 style guidelines
43
+ - **Documentation**: Use clear docstrings and comments
44
+ - **Error Handling**: Implement proper exception handling
45
+ - **Configuration**: Use environment variables for customization
46
+ - **Logging**: Use appropriate log levels and messages
47
+
48
+ ## License
49
+
50
+ By contributing, you agree that your contributions will be licensed under the same terms as this project (research/demonstration use only). You can find the license in [LICENSE](LICENSE.txt).
51
+
52
+ #### Signing Your Work
53
+
54
+ - We require that all contributors "sign-off" on their commits. This certifies that the contribution is your original work, or you have rights to submit it under the same license, or a compatible license.
55
+
56
+ - Any contribution which contains commits that are not Signed-Off will not be accepted.
57
+
58
+ - To sign off on a commit you simply use the `--signoff` (or `-s`) option when committing your changes:
59
+
60
+ ```bash
61
+ $ git commit -s -m "Add cool feature."
62
+ ```
63
+
64
+ This will append the following to your commit message:
65
+
66
+ ```
67
+ Signed-off-by: Your Name <[email protected]>
68
+ ```
69
+
70
+ - Full text of the DCO:
71
+
72
+ ```
73
+ Developer Certificate of Origin
74
+ Version 1.1
75
+
76
+ Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
77
+ 1 Letterman Drive
78
+ Suite D4700
79
+ San Francisco, CA, 94129
80
+
81
+ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
82
+ ```
83
+
84
+ ```
85
+ Developer's Certificate of Origin 1.1
86
+
87
+ By making a contribution to this project, I certify that:
88
+
89
+ (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or
90
+
91
+ (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or
92
+
93
+ (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.
94
+
95
+ (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved.
96
+ ```
backend/DISCLAIMER.txt ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DISCLAIMER
2
+
3
+ IMPORTANT NOTICE - RESEARCH DEMONSTRATION PROTOTYPE
4
+
5
+ This software and all associated code, documentation, and materials (collectively, the "Software") are provided solely for research and demonstration purposes. The Software is intended exclusively as a prototype to demonstrate research concepts and methodologies in the field of artificial intelligence and automated research systems.
6
+
7
+ CRITICAL LIMITATIONS AND WARNINGS:
8
+
9
+ 1. RESEARCH PURPOSE ONLY: The Software is designed and intended solely for academic research, educational demonstration, and experimental purposes. It is NOT intended for production use, commercial deployment, or any real-world application.
10
+
11
+ 2. NO PRODUCTION USE: Under no circumstances should this Software be deployed, used, or relied upon in any production environment, commercial application, or any context where reliability, accuracy, or safety is required.
12
+
13
+ 3. EXPERIMENTAL NATURE: The Software contains experimental features, unproven methodologies, and research-grade implementations that may contain bugs, security vulnerabilities, or other issues that could cause data loss, system instability, or other problems.
14
+
15
+ 4. NO WARRANTIES: THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR ACCURACY.
16
+
17
+ 5. NO LIABILITY: TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER NVIDIA CORPORATION, NOR THE AUTHORS, CONTRIBUTORS, OR ANY OTHER PARTIES INVOLVED IN THE CREATION, PRODUCTION, OR DELIVERY OF THE SOFTWARE SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18
+
19
+ 6. NO SUPPORT: No technical support, maintenance, updates, or assistance is provided for this Software. Users are solely responsible for their use of the Software and any consequences thereof.
20
+
21
+ 7. NO ENDORSEMENT: The inclusion of any third-party components, APIs, or services does not constitute an endorsement of those services or their providers.
22
+
23
+ 8. COMPLIANCE: Users are responsible for ensuring their use of the Software complies with all applicable laws, regulations, and terms of service for any third-party services or APIs used by the Software.
24
+
25
+ 9. SECURITY: The Software has not been subjected to security audits or testing suitable for production environments. Users assume all risks associated with security vulnerabilities.
26
+
27
+ 10. DATA PRIVACY: The Software may process, transmit, or store data in ways that do not comply with data protection regulations. Users are responsible for ensuring compliance with applicable privacy laws.
28
+
29
+ ACKNOWLEDGMENT:
30
+
31
+ By using, copying, modifying, or distributing the Software, you acknowledge that you have read, understood, and agree to be bound by the terms of this disclaimer. If you do not agree to these terms, you must not use the Software.
32
+
33
+ For questions regarding this disclaimer, please contact the authors through the appropriate channels established for this research project.
34
+
35
+ Last updated: 07/15/2025.
backend/README.md ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Universal Deep Research Backend (UDR-B)
2
+
3
+ A FastAPI-based backend service that provides intelligent research and reporting capabilities using large language models and web search APIs. The system can perform comprehensive research on user queries, aggregate findings, and generate detailed reports.
4
+
5
+ This software is provided exclusively for research and demonstration purposes. It is intended solely as a prototype to demonstrate research concepts and methodologies in artificial intelligence and automated research systems.
6
+
7
+ - This software is not intended for production deployment, commercial use, or any real-world application where reliability, accuracy, or safety is required.
8
+ - This software contains experimental features, unproven methodologies, and research-grade implementations that may contain bugs, security vulnerabilities, or other issues.
9
+ - The software is provided "AS IS" without any warranties. Neither NVIDIA Corporation nor the authors shall be liable for any damages arising from the use of this software to the fullest extent permitted by law.
10
+
11
+ By using this software, you acknowledge that you have read and understood the complete DISCLAIMER file and agree to be bound by its terms. For the complete legal disclaimer, please see the [DISCLAIMER](DISCLAIMER.txt) file in this directory.
12
+
13
+ ## Features
14
+
15
+ - **Intelligent Research**: Automated web search and content analysis using Tavily API
16
+ - **Multi-Model Support**: Configurable LLM backends (OpenAI, NVIDIA, local vLLM)
17
+ - **Streaming Responses**: Real-time progress updates via Server-Sent Events
18
+ - **Session Management**: Persistent research sessions with unique identifiers
19
+ - **Flexible Architecture**: Modular design with configurable components
20
+ - **Dry Run Mode**: Testing capabilities with mock data
21
+ - **Advanced Framing**: Custom FrameV4 system for increased reliability of instruction following across all models
22
+
23
+ ## Architecture
24
+
25
+ The backend consists of several key components:
26
+
27
+ - **`main.py`**: FastAPI application with research endpoints
28
+ - **`scan_research.py`**: Core research and reporting logic
29
+ - **`clients.py`**: LLM and search API client management
30
+ - **`frame/`**: Advanced reliability framework (FrameV4)
31
+ - **`items.py`**: Data persistence utilities
32
+ - **`sessions.py`**: Session key generation and management
33
+
34
+ ## Quick Start
35
+
36
+ ### Prerequisites
37
+
38
+ - Python 3.8+
39
+ - API keys for your chosen LLM provider
40
+ - Tavily API key for web search functionality
41
+
42
+ ### Installation
43
+
44
+ #### Option 1: Automated Setup (Recommended)
45
+
46
+ The easiest way to set up the backend is using the provided `setup.py` script:
47
+
48
+ 1. **Clone the repository**:
49
+
50
+ ```bash
51
+ git clone <repository-url>
52
+ cd backend
53
+ ```
54
+
55
+ 2. **Run the setup script**:
56
+
57
+ ```bash
58
+ python3 setup.py
59
+ ```
60
+
61
+ The setup script will:
62
+
63
+ - Check Python version compatibility
64
+ - Create necessary directories (`logs/`, `instances/`, `mock_instances/`)
65
+ - Set up environment configuration (`.env` file)
66
+ - Check for required API key files
67
+ - Install Python dependencies
68
+ - Validate the setup
69
+
70
+ 3. **Configure API keys**:
71
+ Create the following files with your API keys:
72
+
73
+ ```bash
74
+ echo "your-tavily-api-key" > tavily_api.txt
75
+ echo "your-llm-api-key" > nvdev_api.txt # or openai_api.txt
76
+ ```
77
+
78
+ 4. **Start the server**:
79
+
80
+ ```bash
81
+ ./launch_server.sh
82
+ ```
83
+
84
+ **Note**: The `launch_server.sh` script is the recommended way to start the server as it:
85
+
86
+ - Automatically loads environment variables from `.env`
87
+ - Sets proper default configurations
88
+ - Runs the server in the background with logging
89
+ - Provides process management information
90
+
91
+ #### Option 2: Manual Setup
92
+
93
+ If you prefer to set up the backend manually, follow these steps:
94
+
95
+ 1. **Clone the repository**:
96
+
97
+ ```bash
98
+ git clone <repository-url>
99
+ cd backend
100
+ ```
101
+
102
+ 2. **Create virtual environment**:
103
+
104
+ ```bash
105
+ python3 -m venv venv
106
+ source venv/bin/activate # On Windows: venv\Scripts\activate
107
+ ```
108
+
109
+ 3. **Install dependencies**:
110
+
111
+ ```bash
112
+ pip install -r requirements.txt
113
+ ```
114
+
115
+ 4. **Create necessary directories**:
116
+
117
+ ```bash
118
+ mkdir -p logs instances mock_instances
119
+ ```
120
+
121
+ 5. **Set up environment configuration**:
122
+ Copy the example environment file and configure it:
123
+
124
+ ```bash
125
+ cp env.example .env
126
+ # Edit .env file with your configuration
127
+ ```
128
+
129
+ 6. **Configure API keys**:
130
+ Create the following files with your API keys:
131
+
132
+ ```bash
133
+ echo "your-tavily-api-key" > tavily_api.txt
134
+ echo "your-llm-api-key" > nvdev_api.txt # or e.g., openai_api.txt
135
+ ```
136
+
137
+ 7. **Start the server**:
138
+
139
+ ```bash
140
+ ./launch_server.sh
141
+ ```
142
+
143
+ **Note**: As noted for Option 1, the `launch_server.sh` script is the recommended way to start the server as it:
144
+
145
+ - Automatically loads environment variables from `.env`
146
+ - Sets proper default configurations
147
+ - Runs the server in the background with logging
148
+ - Provides process management information
149
+
150
+ The server will be available at `http://localhost:8000`
151
+
152
+ You can now quickly test the server.
153
+
154
+ ```bash
155
+ curl -X POST http://localhost:8000/api/research \
156
+ -H "Content-Type: application/json" \
157
+ -d '{
158
+ "prompt": "What are the latest developments in quantum computing?",
159
+ "start_from": "research"
160
+ }'
161
+ ```
162
+
163
+ ## Configuration
164
+
165
+ ### Environment Variables
166
+
167
+ Create a `.env` file in the backend directory:
168
+
169
+ ```env
170
+ # Server Configuration
171
+ HOST=0.0.0.0
172
+ PORT=8000
173
+ LOG_LEVEL=info
174
+
175
+ # CORS Configuration
176
+ FRONTEND_URL=http://localhost:3000
177
+
178
+ # Model Configuration
179
+ DEFAULT_MODEL=llama-3.1-nemotron-253b
180
+ LLM_BASE_URL=https://integrate.api.nvidia.com/v1
181
+ LLM_API_KEY_FILE=nvdev_api.txt
182
+
183
+ # Search Configuration
184
+ TAVILY_API_KEY_FILE=tavily_api.txt
185
+
186
+ # Research Configuration
187
+ MAX_TOPICS=1
188
+ MAX_SEARCH_PHRASES=1
189
+ MOCK_DIRECTORY=mock_instances/stocks_24th_3_sections
190
+
191
+ # Logging Configuration
192
+ LOG_DIR=logs
193
+ TRACE_ENABLED=true
194
+ ```
195
+
196
+ ### Model Configuration
197
+
198
+ The system supports multiple LLM providers. Configure models in `clients.py`:
199
+
200
+ ```python
201
+ MODEL_CONFIGS = {
202
+ "llama-3.1-8b": {
203
+ "base_url": "https://integrate.api.nvidia.com/v1",
204
+ "api_type": "nvdev",
205
+ "completion_config": {
206
+ "model": "nvdev/meta/llama-3.1-8b-instruct",
207
+ "temperature": 0.2,
208
+ "top_p": 0.7,
209
+ "max_tokens": 2048,
210
+ "stream": True
211
+ }
212
+ },
213
+ # Add more models as needed
214
+ }
215
+ ```
216
+
217
+ ### API Key Files
218
+
219
+ The system expects API keys in text files:
220
+
221
+ - `tavily_api.txt`: Tavily search API key
222
+ - `nvdev_api.txt`: NVIDIA API key
223
+ - `openai_api.txt`: OpenAI API key
224
+
225
+ ## API Endpoints
226
+
227
+ ### GET `/`
228
+
229
+ Health check endpoint that returns a status message.
230
+
231
+ ### POST `/api/research`
232
+
233
+ Main research endpoint that performs research and generates reports.
234
+
235
+ **Request Body**:
236
+
237
+ ```json
238
+ {
239
+ "dry": false,
240
+ "session_key": "optional-session-key",
241
+ "start_from": "research",
242
+ "prompt": "Your research query here",
243
+ "mock_directory": "mock_instances/stocks_24th_3_sections"
244
+ }
245
+ ```
246
+
247
+ **Parameters**:
248
+
249
+ - `dry` (boolean): Use mock data for testing
250
+ - `session_key` (string, optional): Existing session to continue
251
+ - `start_from` (string): "research" or "reporting"
252
+ - `prompt` (string): Research query (required for research phase)
253
+ - `mock_directory` (string): Directory for mock data
254
+
255
+ **Response**: Server-Sent Events stream with research progress
256
+
257
+ ### POST `/api/research2`
258
+
259
+ Advanced reliability framework endpoint using FrameV4 system. This is the endpoint that supports custom user deep research strategies.
260
+
261
+ **Request Body**:
262
+
263
+ ```json
264
+ {
265
+ "prompt": "Your research query",
266
+ "strategy_id": "custom-strategy",
267
+ "strategy_content": "Custom research strategy"
268
+ }
269
+ ```
270
+
271
+ ## Usage Examples
272
+
273
+ ### Basic Research Request
274
+
275
+ ```bash
276
+ curl -X POST http://localhost:8000/api/research \
277
+ -H "Content-Type: application/json" \
278
+ -d '{
279
+ "prompt": "What are the latest developments in quantum computing?",
280
+ "start_from": "research"
281
+ }'
282
+ ```
283
+
284
+ ### Dry Run Testing
285
+
286
+ ```bash
287
+ curl -X POST http://localhost:8000/api/research \
288
+ -H "Content-Type: application/json" \
289
+ -d '{
290
+ "dry": true,
291
+ "prompt": "Test research query",
292
+ "start_from": "research"
293
+ }'
294
+ ```
295
+
296
+ ### Continue from Reporting Phase
297
+
298
+ ```bash
299
+ curl -X POST http://localhost:8000/api/research \
300
+ -H "Content-Type: application/json" \
301
+ -d '{
302
+ "session_key": "20241201T120000Z-abc12345", # This would be the key of the session which you have previously started
303
+ "start_from": "reporting"
304
+ }'
305
+ ```
306
+
307
+ ## Development
308
+
309
+ ### Logging
310
+
311
+ Logs are stored in the `logs/` directory:
312
+
313
+ - `comms_YYYYMMDD_HH-MM-SS.log`: Communication traces
314
+ - `{instance_id}_compilation.log`: Frame compilation logs
315
+ - `{instance_id}_execution.log`: Frame execution logs
316
+
317
+ ### Mock Data
318
+
319
+ Mock research data is available in `mock_instances/`:
320
+
321
+ - `stocks_24th_3_sections/`: Stock market research data
322
+ - `stocks_30th_short/`: Short stock market data
323
+
324
+ ## Deployment
325
+
326
+ ### Production Deployment
327
+
328
+ 1. **Set up environment**:
329
+
330
+ ```bash
331
+ export HOST=0.0.0.0
332
+ export PORT=8000
333
+ export LOG_LEVEL=info
334
+ ```
335
+
336
+ 2. **Run with gunicorn**:
337
+
338
+ ```bash
339
+ pip install gunicorn
340
+ gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
341
+ ```
342
+
343
+ **Note**: For development, prefer using `./launch_server.sh` which provides better process management and logging.
344
+
345
+ ### Docker Deployment
346
+
347
+ Create a `Dockerfile`:
348
+
349
+ ```dockerfile
350
+ FROM python:3.9-slim
351
+
352
+ WORKDIR /app
353
+ COPY requirements.txt .
354
+ RUN pip install -r requirements.txt
355
+
356
+ COPY . .
357
+ EXPOSE 8000
358
+
359
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
360
+ ```
361
+
362
+ ## Troubleshooting
363
+
364
+ ### Common Issues
365
+
366
+ 1. **API Key Errors**: Ensure API key files exist and contain valid keys
367
+ 2. **CORS Errors**: Check `FRONTEND_URL` configuration
368
+ 3. **Model Errors**: Verify model configuration in `clients.py`
369
+ 4. **Permission Errors**: Ensure write permissions for `logs/` and `instances/` directories
370
+
371
+ ### Debug Mode
372
+
373
+ Enable debug logging by setting the LOG_LEVEL environment variable:
374
+
375
+ ```bash
376
+ export LOG_LEVEL=debug
377
+ ./launch_server.sh
378
+ ```
379
+
380
+ Or run uvicorn directly for debugging:
381
+
382
+ ```bash
383
+ uvicorn main:app --reload --log-level=debug
384
+ ```
385
+
386
+ ## Contributing
387
+
388
+ 1. Fork the repository
389
+ 2. Create a feature branch
390
+ 3. Make your changes
391
+ 4. Add tests if applicable
392
+ 5. Submit a pull request
393
+
394
+ ## License and Disclaimer
395
+
396
+ This software is provided for research and demonstration purposes only. Please refer to the [DISCLAIMER](DISCLAIMER.txt) file for complete terms and conditions regarding the use of this software. You can find the license in [LICENSE](LICENSE.txt).
397
+
398
+ **Do not use this code in production.**
399
+
400
+ ## Support
401
+
402
+ For issues and questions:
403
+
404
+ - Create an issue in the repository
405
+ - Check the logs in the `logs/` directory
406
+ - Review the configuration settings
backend/clients.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ Client management module for LLM and search API interactions.
17
+
18
+ This module provides client creation and management for:
19
+ - Large Language Models (OpenAI, NVIDIA, local vLLM)
20
+ - Web search (Tavily API)
21
+ - Configuration-based client setup
22
+ """
23
+
24
+ from typing import Any, Dict, List, Literal, TypedDict
25
+
26
+ from openai import OpenAI
27
+ from tavily import TavilyClient
28
+
29
+ from config import get_config
30
+
31
+ # Get configuration
32
+ config = get_config()
33
+
34
+ # Configuration system
35
+ ApiType = Literal["nvdev", "openai", "tavily"]
36
+
37
+
38
+ class ModelConfig(TypedDict):
39
+ base_url: str
40
+ api_type: ApiType
41
+ completion_config: Dict[str, Any]
42
+
43
+
44
+ # Available model configurations
45
+ MODEL_CONFIGS: Dict[str, ModelConfig] = {
46
+ "llama-3.1-8b": {
47
+ "base_url": "https://integrate.api.nvidia.com/v1",
48
+ "api_type": "nvdev",
49
+ "completion_config": {
50
+ "model": "nvdev/meta/llama-3.1-8b-instruct",
51
+ "temperature": 0.2,
52
+ "top_p": 0.7,
53
+ "max_tokens": 2048,
54
+ "stream": True,
55
+ },
56
+ },
57
+ "llama-3.1-nemotron-8b": {
58
+ "base_url": "https://integrate.api.nvidia.com/v1",
59
+ "api_type": "nvdev",
60
+ "completion_config": {
61
+ "model": "nvdev/nvidia/llama-3.1-nemotron-nano-8b-v1",
62
+ "temperature": 0.2,
63
+ "top_p": 0.7,
64
+ "max_tokens": 2048,
65
+ "stream": True,
66
+ },
67
+ },
68
+ "llama-3.1-nemotron-253b": {
69
+ "base_url": "https://integrate.api.nvidia.com/v1",
70
+ "api_type": "nvdev",
71
+ "completion_config": {
72
+ "model": "nvdev/nvidia/llama-3.1-nemotron-ultra-253b-v1",
73
+ "temperature": 0.2,
74
+ "top_p": 0.7,
75
+ "max_tokens": 2048,
76
+ "stream": True,
77
+ },
78
+ },
79
+ }
80
+
81
+ # Default model to use (from configuration)
82
+ DEFAULT_MODEL = config.model.default_model
83
+
84
+
85
+ def get_api_key(api_type: ApiType) -> str:
86
+ """
87
+ Get the API key for the specified API type.
88
+
89
+ This function reads API keys from configuration-specified files.
90
+ The file paths can be customized via environment variables.
91
+
92
+ Args:
93
+ api_type: The type of API to get the key for ("nvdev", "openai", "tavily")
94
+
95
+ Returns:
96
+ str: The API key from the configured file
97
+
98
+ Raises:
99
+ FileNotFoundError: If the API key file doesn't exist
100
+ ValueError: If the API type is unknown
101
+
102
+ Example:
103
+ >>> get_api_key("tavily")
104
+ "your-tavily-api-key"
105
+ """
106
+ api_key_files = {
107
+ "nvdev": config.model.api_key_file,
108
+ "openai": "openai_api.txt",
109
+ "tavily": config.search.tavily_api_key_file,
110
+ }
111
+
112
+ key_file = api_key_files.get(api_type)
113
+ if not key_file:
114
+ raise ValueError(f"Unknown API type: {api_type}")
115
+
116
+ try:
117
+ with open(key_file, "r") as file:
118
+ return file.read().strip()
119
+ except FileNotFoundError:
120
+ raise FileNotFoundError(
121
+ f"API key file not found for {api_type}. "
122
+ f"Please create {key_file} with your API key. "
123
+ f"See README.md for configuration instructions."
124
+ )
125
+
126
+
127
+ def create_lm_client(model_config: ModelConfig | None = None) -> OpenAI:
128
+ """
129
+ Create an OpenAI client instance with the specified configuration.
130
+
131
+ This function creates a client for the configured LLM provider.
132
+ The client can be customized with specific model configurations
133
+ or will use the default model from configuration.
134
+
135
+ Args:
136
+ model_config: Optional model configuration to override defaults.
137
+ If None, uses the default model from configuration.
138
+
139
+ Returns:
140
+ OpenAI: Configured OpenAI client instance
141
+
142
+ Example:
143
+ >>> client = create_lm_client()
144
+ >>> response = client.chat.completions.create(...)
145
+ """
146
+ model_config = model_config or MODEL_CONFIGS[DEFAULT_MODEL]
147
+ api_key = get_api_key(model_config["api_type"])
148
+
149
+ return OpenAI(base_url=model_config["base_url"], api_key=api_key)
150
+
151
+
152
+ def create_tavily_client() -> TavilyClient:
153
+ """
154
+ Create a Tavily client instance for web search functionality.
155
+
156
+ This function creates a client for the Tavily search API using
157
+ the API key from the configured file path.
158
+
159
+ Returns:
160
+ TavilyClient: Configured Tavily client instance
161
+
162
+ Raises:
163
+ FileNotFoundError: If the Tavily API key file is not found
164
+
165
+ Example:
166
+ >>> client = create_tavily_client()
167
+ >>> results = client.search("quantum computing")
168
+ """
169
+ api_key = get_api_key("tavily")
170
+ return TavilyClient(api_key=api_key)
171
+
172
+
173
+ def get_completion(
174
+ client: OpenAI,
175
+ messages: List[Dict[str, Any]],
176
+ model_config: ModelConfig | None = None,
177
+ ) -> str:
178
+ """
179
+ Get completion from the OpenAI client using the specified model configuration.
180
+
181
+ This function handles both streaming and non-streaming completions,
182
+ with special handling for certain model configurations that require
183
+ specific message formatting.
184
+
185
+ Args:
186
+ client: OpenAI client instance
187
+ messages: List of messages for the completion
188
+ model_config: Optional model configuration to override defaults.
189
+ If None, uses the default model configuration.
190
+
191
+ Returns:
192
+ str: The completion text
193
+
194
+ Example:
195
+ >>> client = create_lm_client()
196
+ >>> messages = [{"role": "user", "content": "Hello"}]
197
+ >>> response = get_completion(client, messages)
198
+ >>> print(response)
199
+ "Hello! How can I help you today?"
200
+ """
201
+ model_config = model_config or MODEL_CONFIGS[DEFAULT_MODEL]
202
+
203
+ # Handle special model configurations
204
+ if "retarded" in model_config and model_config["retarded"]:
205
+ if messages[0]["role"] == "system":
206
+ first_message = messages[0]
207
+ messages = [msg for msg in messages if msg["role"] != "system"]
208
+ messages[0]["content"] = (
209
+ first_message["content"] + "\n\n" + messages[0]["content"]
210
+ )
211
+ messages.insert(0, {"role": "system", "content": "detailed thinking off"})
212
+
213
+ completion = client.chat.completions.create(
214
+ messages=messages, **model_config["completion_config"]
215
+ )
216
+
217
+ # Handle streaming vs non-streaming responses
218
+ if model_config["completion_config"]["stream"]:
219
+ ret = ""
220
+ for chunk in completion:
221
+ if chunk.choices[0].delta.content:
222
+ ret += chunk.choices[0].delta.content
223
+ else:
224
+ ret = completion.choices[0].message.content
225
+
226
+ return ret
227
+
228
+
229
+ def is_output_positive(output: str) -> bool:
230
+ """
231
+ Check if the output contains positive indicators.
232
+
233
+ This function checks if the given output string contains
234
+ positive words like "yes" or "true" (case-insensitive).
235
+
236
+ Args:
237
+ output: The string to check for positive indicators
238
+
239
+ Returns:
240
+ bool: True if positive indicators are found, False otherwise
241
+
242
+ Example:
243
+ >>> is_output_positive("Yes, that's correct")
244
+ True
245
+ >>> is_output_positive("No, that's not right")
246
+ False
247
+ """
248
+ positive_words = ["yes", "true"]
249
+ return any(word in output.lower() for word in positive_words)
backend/config.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ Configuration module for the Universal Deep Research Backend (UDR-B).
17
+
18
+ This module centralizes all configurable settings and provides
19
+ environment variable support for easy deployment customization.
20
+ """
21
+
22
+ import os
23
+ from dataclasses import dataclass, field
24
+ from typing import Any, Dict, Optional
25
+
26
+ from dotenv import load_dotenv
27
+
28
+ # Load environment variables from .env file
29
+ load_dotenv()
30
+
31
+
32
+ @dataclass
33
+ class ServerConfig:
34
+ """Server configuration settings."""
35
+
36
+ host: str = field(default_factory=lambda: os.getenv("HOST", "0.0.0.0"))
37
+ port: int = field(default_factory=lambda: int(os.getenv("PORT", "8000")))
38
+ log_level: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "info"))
39
+ reload: bool = field(
40
+ default_factory=lambda: os.getenv("RELOAD", "true").lower() == "true"
41
+ )
42
+
43
+
44
+ @dataclass
45
+ class CORSConfig:
46
+ """CORS configuration settings."""
47
+
48
+ frontend_url: str = field(
49
+ default_factory=lambda: os.getenv("FRONTEND_URL", "http://localhost:3000")
50
+ )
51
+ allow_credentials: bool = field(
52
+ default_factory=lambda: os.getenv("ALLOW_CREDENTIALS", "true").lower() == "true"
53
+ )
54
+ allow_methods: list = field(default_factory=lambda: ["*"])
55
+ allow_headers: list = field(default_factory=lambda: ["*"])
56
+
57
+
58
+ @dataclass
59
+ class ModelConfig:
60
+ """Model configuration settings."""
61
+
62
+ default_model: str = field(
63
+ default_factory=lambda: os.getenv("DEFAULT_MODEL", "llama-3.1-nemotron-253b")
64
+ )
65
+ base_url: str = field(
66
+ default_factory=lambda: os.getenv(
67
+ "LLM_BASE_URL", "https://integrate.api.nvidia.com/v1"
68
+ )
69
+ )
70
+ api_key_file: str = field(
71
+ default_factory=lambda: os.getenv("LLM_API_KEY_FILE", "nvdev_api.txt")
72
+ )
73
+ temperature: float = field(
74
+ default_factory=lambda: float(os.getenv("LLM_TEMPERATURE", "0.2"))
75
+ )
76
+ top_p: float = field(default_factory=lambda: float(os.getenv("LLM_TOP_P", "0.7")))
77
+ max_tokens: int = field(
78
+ default_factory=lambda: int(os.getenv("LLM_MAX_TOKENS", "2048"))
79
+ )
80
+
81
+
82
+ @dataclass
83
+ class SearchConfig:
84
+ """Search configuration settings."""
85
+
86
+ tavily_api_key_file: str = field(
87
+ default_factory=lambda: os.getenv("TAVILY_API_KEY_FILE", "tavily_api.txt")
88
+ )
89
+ max_search_results: int = field(
90
+ default_factory=lambda: int(os.getenv("MAX_SEARCH_RESULTS", "10"))
91
+ )
92
+
93
+
94
+ @dataclass
95
+ class ResearchConfig:
96
+ """Research configuration settings."""
97
+
98
+ max_topics: int = field(default_factory=lambda: int(os.getenv("MAX_TOPICS", "1")))
99
+ max_search_phrases: int = field(
100
+ default_factory=lambda: int(os.getenv("MAX_SEARCH_PHRASES", "1"))
101
+ )
102
+ mock_directory: str = field(
103
+ default_factory=lambda: os.getenv(
104
+ "MOCK_DIRECTORY", "mock_instances/stocks_24th_3_sections"
105
+ )
106
+ )
107
+ random_seed: int = field(
108
+ default_factory=lambda: int(os.getenv("RANDOM_SEED", "42"))
109
+ )
110
+
111
+
112
+ @dataclass
113
+ class LoggingConfig:
114
+ """Logging configuration settings."""
115
+
116
+ log_dir: str = field(default_factory=lambda: os.getenv("LOG_DIR", "logs"))
117
+ trace_enabled: bool = field(
118
+ default_factory=lambda: os.getenv("TRACE_ENABLED", "true").lower() == "true"
119
+ )
120
+ copy_into_stdout: bool = field(
121
+ default_factory=lambda: os.getenv("COPY_INTO_STDOUT", "false").lower() == "true"
122
+ )
123
+
124
+
125
+ @dataclass
126
+ class FrameConfig:
127
+ """FrameV4 configuration settings."""
128
+
129
+ long_context_cutoff: int = field(
130
+ default_factory=lambda: int(os.getenv("LONG_CONTEXT_CUTOFF", "8192"))
131
+ )
132
+ force_long_context: bool = field(
133
+ default_factory=lambda: os.getenv("FORCE_LONG_CONTEXT", "false").lower()
134
+ == "true"
135
+ )
136
+ max_iterations: int = field(
137
+ default_factory=lambda: int(os.getenv("MAX_ITERATIONS", "1024"))
138
+ )
139
+ interaction_level: str = field(
140
+ default_factory=lambda: os.getenv("INTERACTION_LEVEL", "none")
141
+ )
142
+
143
+
144
+ @dataclass
145
+ class AppConfig:
146
+ """Main application configuration."""
147
+
148
+ server: ServerConfig = field(default_factory=ServerConfig)
149
+ cors: CORSConfig = field(default_factory=CORSConfig)
150
+ model: ModelConfig = field(default_factory=ModelConfig)
151
+ search: SearchConfig = field(default_factory=SearchConfig)
152
+ research: ResearchConfig = field(default_factory=ResearchConfig)
153
+ logging: LoggingConfig = field(default_factory=LoggingConfig)
154
+ frame: FrameConfig = field(default_factory=FrameConfig)
155
+
156
+ def __post_init__(self):
157
+ """Ensure log directory exists."""
158
+ os.makedirs(self.logging.log_dir, exist_ok=True)
159
+
160
+ def to_dict(self) -> Dict[str, Any]:
161
+ """Convert configuration to dictionary."""
162
+ return {
163
+ "server": {
164
+ "host": self.server.host,
165
+ "port": self.server.port,
166
+ "log_level": self.server.log_level,
167
+ "reload": self.server.reload,
168
+ },
169
+ "cors": {
170
+ "frontend_url": self.cors.frontend_url,
171
+ "allow_credentials": self.cors.allow_credentials,
172
+ "allow_methods": self.cors.allow_methods,
173
+ "allow_headers": self.cors.allow_headers,
174
+ },
175
+ "model": {
176
+ "default_model": self.model.default_model,
177
+ "base_url": self.model.base_url,
178
+ "api_key_file": self.model.api_key_file,
179
+ "temperature": self.model.temperature,
180
+ "top_p": self.model.top_p,
181
+ "max_tokens": self.model.max_tokens,
182
+ },
183
+ "search": {
184
+ "tavily_api_key_file": self.search.tavily_api_key_file,
185
+ "max_search_results": self.search.max_search_results,
186
+ },
187
+ "research": {
188
+ "max_topics": self.research.max_topics,
189
+ "max_search_phrases": self.research.max_search_phrases,
190
+ "mock_directory": self.research.mock_directory,
191
+ "random_seed": self.research.random_seed,
192
+ },
193
+ "logging": {
194
+ "log_dir": self.logging.log_dir,
195
+ "trace_enabled": self.logging.trace_enabled,
196
+ "copy_into_stdout": self.logging.copy_into_stdout,
197
+ },
198
+ "frame": {
199
+ "long_context_cutoff": self.frame.long_context_cutoff,
200
+ "force_long_context": self.frame.force_long_context,
201
+ "max_iterations": self.frame.max_iterations,
202
+ "interaction_level": self.frame.interaction_level,
203
+ },
204
+ }
205
+
206
+
207
+ # Global configuration instance
208
+ config = AppConfig()
209
+
210
+
211
+ def get_config() -> AppConfig:
212
+ """Get the global configuration instance."""
213
+ return config
214
+
215
+
216
+ def update_config(**kwargs) -> None:
217
+ """Update configuration with new values."""
218
+ for key, value in kwargs.items():
219
+ if hasattr(config, key):
220
+ setattr(config, key, value)
221
+ else:
222
+ raise ValueError(f"Unknown configuration key: {key}")
223
+
224
+
225
+ def get_model_configs() -> Dict[str, Dict[str, Any]]:
226
+ """Get the model configurations from clients.py with configurable values."""
227
+ from clients import MODEL_CONFIGS
228
+
229
+ # Update model configs with environment variables
230
+ updated_configs = {}
231
+ for model_name, model_config in MODEL_CONFIGS.items():
232
+ updated_config = model_config.copy()
233
+
234
+ # Override with environment variables if available
235
+ env_prefix = f"{model_name.upper().replace('-', '_')}_"
236
+
237
+ if os.getenv(f"{env_prefix}BASE_URL"):
238
+ updated_config["base_url"] = os.getenv(f"{env_prefix}BASE_URL")
239
+
240
+ if os.getenv(f"{env_prefix}MODEL"):
241
+ updated_config["completion_config"]["model"] = os.getenv(
242
+ f"{env_prefix}MODEL"
243
+ )
244
+
245
+ if os.getenv(f"{env_prefix}TEMPERATURE"):
246
+ updated_config["completion_config"]["temperature"] = float(
247
+ os.getenv(f"{env_prefix}TEMPERATURE")
248
+ )
249
+
250
+ if os.getenv(f"{env_prefix}TOP_P"):
251
+ updated_config["completion_config"]["top_p"] = float(
252
+ os.getenv(f"{env_prefix}TOP_P")
253
+ )
254
+
255
+ if os.getenv(f"{env_prefix}MAX_TOKENS"):
256
+ updated_config["completion_config"]["max_tokens"] = int(
257
+ os.getenv(f"{env_prefix}MAX_TOKENS")
258
+ )
259
+
260
+ updated_configs[model_name] = updated_config
261
+
262
+ return updated_configs
backend/env.example ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Universal Deep Research Backend (UDR-B) - Environment Configuration
2
+ # Copy this file to .env and customize the values for your deployment
3
+
4
+ # Server Configuration
5
+ HOST=0.0.0.0
6
+ PORT=8000
7
+ LOG_LEVEL=info
8
+
9
+ # CORS Configuration
10
+ FRONTEND_URL=http://localhost:3000
11
+ ALLOW_CREDENTIALS=true
12
+
13
+ # Model Configuration
14
+ DEFAULT_MODEL=llama-3.1-nemotron-253b
15
+ LLM_BASE_URL=https://integrate.api.nvidia.com/v1
16
+ LLM_API_KEY_FILE=nvdev_api.txt
17
+ LLM_TEMPERATURE=0.2
18
+ LLM_TOP_P=0.7
19
+ LLM_MAX_TOKENS=2048
20
+
21
+ # Search Configuration
22
+ TAVILY_API_KEY_FILE=tavily_api.txt
23
+ MAX_SEARCH_RESULTS=10
24
+
25
+ # Research Configuration
26
+ MAX_TOPICS=1
27
+ MAX_SEARCH_PHRASES=1
28
+ MOCK_DIRECTORY=mock_instances/stocks_24th_3_sections
29
+ RANDOM_SEED=42
30
+
31
+ # Logging Configuration
32
+ LOG_DIR=logs
33
+ TRACE_ENABLED=true
34
+ COPY_INTO_STDOUT=false
35
+
36
+ # FrameV4 Configuration
37
+ LONG_CONTEXT_CUTOFF=8192
38
+ FORCE_LONG_CONTEXT=false
39
+ MAX_ITERATIONS=1024
40
+ INTERACTION_LEVEL=none
41
+
42
+ # Model-specific overrides (optional)
43
+ # LLAMA_3_1_8B_BASE_URL=https://integrate.api.nvidia.com/v1
44
+ # LLAMA_3_1_8B_MODEL=nvdev/meta/llama-3.1-8b-instruct
45
+ # LLAMA_3_1_8B_TEMPERATURE=0.2
46
+ # LLAMA_3_1_8B_TOP_P=0.7
47
+ # LLAMA_3_1_8B_MAX_TOKENS=2048
backend/frame/__init__.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
backend/frame/clients.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from .trace import Trace
16
+
17
+
18
+ class Client:
19
+ def __init__(self, trace: Trace = None) -> None:
20
+ self.trace = trace
21
+
22
+ def run(self, pre_prompt: str, prompt: str, completion_config: dict = {}) -> str:
23
+ self.trace_query(pre_prompt, prompt, completion_config)
24
+
25
+ def run_messages(
26
+ self,
27
+ messages: list[dict],
28
+ trace_input_messages: bool = True,
29
+ completion_config: dict = {},
30
+ ) -> list[dict]:
31
+ if trace_input_messages:
32
+ self.trace.write_separator()
33
+ for message in messages:
34
+ self.trace_message(message)
35
+
36
+ def trace_message(self, message: dict) -> str:
37
+ if self.trace is not None:
38
+ self.trace(f"<<{message['role']}>>")
39
+ self.trace(message["content"])
40
+
41
+ def trace_query(
42
+ self, pre_prompt: str, prompt: str, completion_config: dict = {}
43
+ ) -> str:
44
+ if self.trace is not None:
45
+ self.trace.write_separator()
46
+ self.trace("<<PRE-PROMPT>>")
47
+ self.trace(pre_prompt)
48
+ self.trace("<<PROMPT>>")
49
+ self.trace(prompt)
50
+
51
+ def trace_response(self, response: str) -> str:
52
+ if self.trace is not None:
53
+ self.trace("<<RESPONSE>>")
54
+ self.trace(response)
55
+
56
+
57
+ class HuggingFaceClient(Client):
58
+ def __init__(
59
+ self,
60
+ model: str = None,
61
+ pipeline_configuration: dict = {},
62
+ seed: int = 42,
63
+ api_key: str | None = None,
64
+ trace: Trace = None,
65
+ ) -> None:
66
+ super().__init__(trace=trace)
67
+
68
+ # to decrease the chance of hard-to-track bugs, we don't allow the 'model' key in pipeline_configuration
69
+ if "model" in pipeline_configuration:
70
+ raise ValueError(
71
+ "The 'model' key is not allowed in pipeline_configuration. Use the 'model' parameter instead."
72
+ )
73
+
74
+ # data members
75
+ self.model = model
76
+ self.seed = seed
77
+ if api_key is None:
78
+ import os
79
+
80
+ self.api_key = os.getenv("HUGGINGFACE_API_KEY")
81
+ self.api_key = api_key
82
+
83
+ # complete pipeline config
84
+ from transformers import pipeline
85
+
86
+ default_configuration = {
87
+ "model": "meta-llama/Llama-3.1-8B-Instruct",
88
+ "device": "cuda:0",
89
+ "max_new_tokens": 1024,
90
+ }
91
+ merged_configuration = {**default_configuration, **pipeline_configuration}
92
+
93
+ # model
94
+ if self.model is not None:
95
+ merged_configuration["model"] = self.model
96
+
97
+ # seed
98
+ from transformers import set_seed
99
+
100
+ set_seed(self.seed)
101
+
102
+ # tokenizer and pipeline for generation
103
+ from transformers import AutoTokenizer
104
+
105
+ self.tokenizer = AutoTokenizer.from_pretrained(
106
+ default_configuration["model"], api_key=self.api_key
107
+ )
108
+ self.generator = pipeline("text-generation", **merged_configuration)
109
+
110
+ def run(self, pre_prompt: str, prompt: str, completion_config: dict = {}) -> str:
111
+ super().run(pre_prompt, prompt, completion_config)
112
+ messages = [
113
+ {"role": "system", "content": pre_prompt},
114
+ {"role": "user", "content": prompt},
115
+ ]
116
+
117
+ completion = self.generator(
118
+ messages,
119
+ **completion_config,
120
+ pad_token_id=self.generator.tokenizer.eos_token_id,
121
+ )
122
+
123
+ ret = completion[0]["generated_text"][-1]["content"]
124
+ self.trace_response(ret)
125
+ return ret
126
+
127
+ def run_messages(
128
+ self, messages, trace_input_messages: bool = True, completion_config={}
129
+ ) -> list[dict]:
130
+ super().run_messages(messages, trace_input_messages, completion_config)
131
+ completion = self.generator(
132
+ messages,
133
+ **completion_config,
134
+ pad_token_id=self.generator.tokenizer.eos_token_id,
135
+ )
136
+
137
+ ret = completion[0]["generated_text"]
138
+ self.trace_message(ret[-1])
139
+ return ret
140
+
141
+
142
+ class OpenAIClient(Client):
143
+ def __init__(
144
+ self,
145
+ base_url: str,
146
+ model: str = None,
147
+ seed: int = 42,
148
+ api_key: str | None = None,
149
+ trace: Trace = None,
150
+ ) -> None:
151
+ super().__init__(trace=trace)
152
+
153
+ self.base_url = base_url
154
+ self.model = (
155
+ model
156
+ if model is not None
157
+ else "nvdev/nvidia/llama-3.1-nemotron-70b-instruct"
158
+ )
159
+ if api_key is None:
160
+ import os
161
+
162
+ api_key = os.getenv("NGC_API_KEY")
163
+ self.api_key = api_key
164
+ self.seed = seed
165
+
166
+ from openai import OpenAI
167
+
168
+ self.client = OpenAI(base_url=self.base_url, api_key=self.api_key)
169
+
170
+ def _invoke(self, messages: list[dict], completion_config: dict = {}) -> str:
171
+ default_settings = {
172
+ "model": self.model,
173
+ "top_p": 1,
174
+ "temperature": 0.0,
175
+ "max_tokens": 2048,
176
+ "stream": True,
177
+ "seed": self.seed,
178
+ }
179
+
180
+ merged_settings = {**default_settings, **completion_config}
181
+ # go through the messages; if the role="ipython" rename it to "function"
182
+ for message in messages:
183
+ if message["role"] == "ipython":
184
+ message["role"] = "function"
185
+ completion = self.client.chat.completions.create(
186
+ messages=messages, **merged_settings
187
+ )
188
+
189
+ ret: str = ""
190
+ for chunk in completion:
191
+ if chunk.choices[0].delta.content is not None:
192
+ ret += chunk.choices[0].delta.content
193
+
194
+ return ret
195
+
196
+ def run(self, pre_prompt: str, prompt: str, completion_config: dict = {}) -> str:
197
+ super().run(pre_prompt, prompt, completion_config)
198
+
199
+ ret = self._invoke(
200
+ messages=[
201
+ {"role": "system", "content": pre_prompt},
202
+ {"role": "user", "content": prompt},
203
+ ],
204
+ completion_config=completion_config,
205
+ )
206
+
207
+ self.trace_response(ret)
208
+ return ret
209
+
210
+ def run_messages(
211
+ self, messages, trace_input_messages: bool = True, completion_config={}
212
+ ) -> list[dict]:
213
+ super().run_messages(messages, trace_input_messages, completion_config)
214
+
215
+ ret_str: str = self._invoke(
216
+ messages=messages, completion_config=completion_config
217
+ )
218
+ ret_msg: dict = {"role": "assistant", "content": ret_str}
219
+
220
+ self.trace_message(ret_msg)
221
+ return [*messages, ret_msg]
backend/frame/errands/message_code_call.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant for generating a Python function call snippet. Your task is to process the MESSAGE, the generated CODE (a function definition), and the TIDINGS (a set of variable names and values). Your goal is to output a single line of Python code that calls the function defined in CODE, passing the variable names from TIDINGS as arguments (not their values), and captures the return values into variables named __output and __vars. The function defined in CODE always returns a tuple (output: str, variables: dict[str, Any]).
2
+
3
+ ===SEPARATOR===
4
+ You are given:
5
+ - MESSAGE: the original user message (for context) that was used to produce CODE
6
+ - CODE: a Python function definition (already generated)
7
+ - TIDINGS: a set of variable names and values (as would be passed to the function), some of which might be useful to pass to CODE in order to accomplish the intent of code in MESSAGE
8
+
9
+ Your job is to:
10
+ 1. Output a single line of Python code that calls the function defined in CODE, passing the variable names from TIDINGS as named arguments (keyword arguments) where applicable/relevant/good fit.
11
+ 2. Capture the return values into variables named __output and __vars, like this:
12
+ __output, __vars = function_from_CODE(var1=var1, var2=var2, ...)
13
+ where var1, var2, ... are the variable names from TIDINGS.
14
+ 3. Output only the Python code line, with no extra text, comments, or formatting.
15
+
16
+ If there are no tidings, call the function defined in CODE with no arguments:
17
+ __output, __vars = function_from_CODE()
18
+
19
+ This is the end of the instructions. Below is the input to process.
20
+
21
+ MESSAGE:
22
+ {message}
23
+
24
+ CODE:
25
+ {code}
26
+
27
+ TIDINGS:
28
+ {tidings}
29
+
30
+ Reminders:
31
+ - Output just the function call to the functiond defined in CODE so that the intent of MESSAGE (from which CODE was generated) is fulfilled.
32
+ - To pass variables into the function call, use variable names described under TIDINGS.
33
+ - Do not output any other text. Do not output any surrounding backticks or code tags.
backend/frame/errands/message_code_processing.txt ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant for extracting and preparing executable Python code from user messages. Your task is to process the MESSAGE below, which may contain code (in any language or pseudo-code), natural language instructions, or a combination of both. Your goal is to output only the final, executable Python code, wrapped in a function named `code`, making any necessary modifications as described below. The function must return a tuple: (output: str, variables: dict[str, Any]), where the first element is the output string (what would be printed) and the second is a dictionary of all variables that have been modified in the original code segments/code modification instructions provided by the user.
2
+
3
+ ===SEPARATOR===
4
+ You are given a MESSAGE that may contain:
5
+ - Python code, code in another language, or pseudo-code
6
+ - Natural language instructions that may request modifications to code provided, corrections, or translation to Python
7
+ - A mix of code blocks (e.g., in markdown backticks, <code> tags, or inline) and surrounding text
8
+
9
+ You are also given TIDINGS, which are variables, values, or context objects provided by the system. If you decide to use any of the tidings, the same name must also appear as an argument. Tidings are not globals -- they will be passed to the generated function in a function call as arguments. Each tiding is provided as a name, value, and description. If the MESSAGE requests code that uses a variable or value present in the tidings, use the tiding as an argument in the function. If the MESSAGE does not reference any tidings, you may ignore them.
10
+
11
+ Your job is to:
12
+ 1. Extract all code that is intended for execution, as indicated by the message and its instructions.
13
+ 2. If the code is not in Python, translate it to Python as faithfully as possible, using standard libraries and idiomatic Python where appropriate.
14
+ 3. If the code is pseudo-code or contains small errors (e.g., typos, misspelled variable names, or incorrect standard library usage), correct these issues to produce valid, executable Python code.
15
+ 4. If the surrounding instructions request modifications (e.g., change a variable name, add a print statement, use a different algorithm), make those changes in the final code.
16
+ 5. If there are multiple code blocks or fragments, combine them as needed to produce a single, coherent Python function that fulfills the user's intent.
17
+ 6. Wrap all code in a function named `code`. The function must have a docstring describing what it does, based on the user's message and intent.
18
+ 7. Any reference to external variables (not defined within the function) should be marked with a comment indicating that the variable is external and must be provided by the caller, unless it is provided as a tiding.
19
+ 8. Any output that would be printed should instead be appended to a string accumulator (e.g., `__output`), and the function should return a tuple: (output, variables), where `output` is the string and `variables` is a dictionary of all variables that have been either modified in the original code segment present in the message or added/modified by the surrounding text.
20
+ 9. Do not include any explanatory text, comments about your process, or formatting (such as markdown backticks) in your output—output only the final Python function definition.
21
+
22
+ Examples:
23
+
24
+ Example 1:
25
+ MESSAGE:
26
+ Please execute the following code:
27
+ ```
28
+ x = 5
29
+ y = 10
30
+ print(x + y)
31
+ ```
32
+ TIDINGS:
33
+
34
+ OUTPUT:
35
+ def code():
36
+ """Execute the given code and return the output string and modified variables as a tuple."""
37
+ output = ""
38
+ try:
39
+ x = 5
40
+ y = 10
41
+ output += str(x + y) + "\n"
42
+ return output, { 'x': x, 'y': y }
43
+ except Exception as e:
44
+ return output + "\n\n" + str(e), { 'x': x, 'y': y }
45
+
46
+ Example 2:
47
+ MESSAGE:
48
+ Run this JavaScript code, but in Python:
49
+ ```
50
+ let arr = [1, 2, 3];
51
+ console.log(arr.map(x => x * 2));
52
+ ```
53
+ TIDINGS:
54
+
55
+ OUTPUT:
56
+ def code():
57
+ """Double each element in the array and return the result string and newly defined / modified variables as a tuple."""
58
+ output = ""
59
+ try:
60
+ arr = [1, 2, 3]
61
+ doubled = [x * 2 for x in arr]
62
+ output += str(doubled) + "\n"
63
+ return output, { 'arr': arr, 'doubled': doubled }
64
+ except Exception as e:
65
+ return output + "\n\n" + str(e), { 'arr': arr, 'doubled': doubled }
66
+
67
+ Example 3:
68
+ MESSAGE:
69
+ Here's some pseudo-code. Please execute it after translating to Python:
70
+ - Set total to 0
71
+ - For each number in [1,2,3,4], add it to total
72
+ - Respond with total to me
73
+ TIDINGS:
74
+
75
+ OUTPUT:
76
+ def code():
77
+ """Sum the numbers in the list and return the total string and newly defined / modified variables as a tuple."""
78
+ output = ""
79
+ try:
80
+ total = 0
81
+ for number in [1, 2, 3, 4]:
82
+ total += number
83
+ output += str(total) + "\n"
84
+ return output, { 'total': total }
85
+ except Exception as e:
86
+ return output + "\n\n" + str(e), { 'total': total }
87
+
88
+ Example 4:
89
+ MESSAGE:
90
+ Execute this, but change the variable name from 'foo' to 'bar':
91
+ ```
92
+ foo = 42
93
+ print(foo)
94
+ ```
95
+ TIDINGS:
96
+
97
+ OUTPUT:
98
+ def code():
99
+ """Assign 42 to bar and return its value string and variables as a tuple."""
100
+ output = ""
101
+ try:
102
+ bar = 42
103
+ output += str(bar) + "\n"
104
+ return output, { 'bar': bar }
105
+ except Exception as e:
106
+ return output + "\n\n" + str(e), { 'bar': bar }
107
+
108
+ Example 5:
109
+ MESSAGE:
110
+ Please run this code (note: there is a typo in the function name):
111
+ ```
112
+ pritn('Hello, world!')
113
+ ```
114
+ TIDINGS:
115
+
116
+ OUTPUT:
117
+ def code():
118
+ """Print 'Hello, world!' and return the output string and variables as a tuple."""
119
+ output = ""
120
+ try:
121
+ output += 'Hello, world!\n'
122
+ return output, {}
123
+ except Exception as e:
124
+ return output + "\n\n" + str(e), {}
125
+
126
+ Example 6:
127
+ MESSAGE:
128
+ Use the provided user_name to greet the user.
129
+ TIDINGS:
130
+ user_name: Alice # The name of the user to greet
131
+
132
+ OUTPUT:
133
+ def code(user_name):
134
+ """Greet the user by name using the provided user_name tiding, and return the output string and new/modified variables as a tuple."""
135
+ output = ""
136
+ try:
137
+ output += f"Hello, {user_name}!\n"
138
+ return output, { }
139
+ except Exception as e:
140
+ return output + "\n\n" + str(e), { }
141
+
142
+ Reminders:
143
+ - Output only the final, executable Python function definition, with no extra text or formatting.
144
+ - Make reasonable corrections and modifications as requested or needed.
145
+ - If translation or correction is not possible, output the closest valid Python function that fulfills the user's intent.
146
+ - The function must always return a tuple: (output, variables), where output is a string and variables is a dictionary.
147
+ - Do not output any surrounding backticks (```) or code tags—output just the code directly.
148
+
149
+ This is the end of the classification instructions. Below is the input to classify.
150
+
151
+ MESSAGE:
152
+ {message}
153
+ TIDINGS:
154
+ {tidings}
backend/frame/errands/message_code_skill_processing.txt ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant for extracting and preparing Python class and function definitions from user messages. Your task is to process the MESSAGE below, which may contain code (in any language or pseudo-code), natural language instructions, or a combination of both. Your goal is to output only the final, properly formatted Python class and/or function definitions that should be stored as reusable skills, making any necessary modifications as described below.
2
+
3
+ ===SEPARATOR===
4
+ You are given a MESSAGE that may contain:
5
+ - Python code, code in another language, or pseudo-code
6
+ - Natural language instructions that may request modifications to the code, corrections, or translation to Python
7
+ - A mix of code blocks (e.g., in markdown backticks, <code> tags, or inline) and surrounding text
8
+
9
+ Your job is to:
10
+ 1. Extract all code that is intended to be stored as a reusable skill (i.e., class or function definitions), as indicated by the message and its instructions.
11
+ 2. If the code is not in Python, translate it to Python as faithfully as possible, using standard libraries and idiomatic Python where appropriate.
12
+ 3. If the code is pseudo-code, incomplete, or contains small errors (e.g., typos, misspelled variable names, or incorrect standard library usage), correct these issues to produce valid, well-formed Python class/function definitions.
13
+ 4. If the surrounding instructions request modifications (e.g., change argument names, add docstrings, use a different algorithm, add or adjust comments), make those changes in the final code.
14
+ 5. If the message provides only a code body, or describes arguments or docstrings in natural language, construct the appropriate function or class definition, inferring argument names, types, and docstrings as needed from the context.
15
+ 6. All class and function definitions should be type-annotated wherever possible, and include informative docstrings that describe their purpose, arguments, and return values.
16
+ 7. Comments provided in the input should be preserved and adjusted as necessary. Add comments to the code wherever the operations performed are not easily understood at a glance.
17
+ 8. If there are multiple class or function definitions, output them together as a single, coherent Python module.
18
+ 9. Do not include any explanatory text, comments about your process, or formatting (such as markdown backticks or code tags) in your output—output only the final Python class and function definitions.
19
+
20
+ Examples:
21
+
22
+ Example 1:
23
+ MESSAGE:
24
+ Please store this function for future use:
25
+ ```
26
+ def add(a, b):
27
+ return a + b
28
+ ```
29
+ Add type annotations and a docstring.
30
+
31
+ OUTPUT:
32
+ def add(a: int, b: int) -> int:
33
+ """Add two integers and return the result."""
34
+ return a + b
35
+
36
+ Example 2:
37
+ MESSAGE:
38
+ Convert this JavaScript function to Python and make it reusable:
39
+ ```
40
+ function greet(name) {
41
+ // Print a greeting
42
+ console.log('Hello, ' + name);
43
+ }
44
+ ```
45
+
46
+ OUTPUT:
47
+ def greet(name: str) -> None:
48
+ """Print a greeting to the given name."""
49
+ # Print a greeting
50
+ print(f"Hello, {name}")
51
+
52
+ Example 3:
53
+ MESSAGE:
54
+ Here's a code body. Please turn it into a function that takes a list of numbers and returns their mean. Add type annotations and a docstring.
55
+ ```
56
+ total = 0
57
+ for x in numbers:
58
+ total += x
59
+ return total / len(numbers)
60
+ ```
61
+
62
+ OUTPUT:
63
+ def mean(numbers: list[float]) -> float:
64
+ """Calculate and return the mean of a list of numbers."""
65
+ total = 0
66
+ for x in numbers:
67
+ total += x
68
+ return total / len(numbers)
69
+
70
+ Example 4:
71
+ MESSAGE:
72
+ Hi, I will now teach you how to handle a person entity. Below is some code functionality for what I have in mind:
73
+ ```
74
+ class Person:
75
+ def __init__(self, name: str, age: int) -> None:
76
+ self.name = name # The person's name
77
+ self.age = age # The person's age
78
+
79
+ def greet(self) -> str:
80
+ return f"Hello, my name is {self.name}."
81
+ ```
82
+
83
+ OUTPUT:
84
+ class Person:
85
+ """A class representing a person with a name and age."""
86
+
87
+ def __init__(self, name: str, age: int) -> None:
88
+ """Initialize a new Person with a name and age."""
89
+ self.name = name # The person's name
90
+ self.age = age # The person's age
91
+
92
+ def greet(self) -> str:
93
+ """Return a greeting message including the person's name."""
94
+ return f"Hello, my name is {self.name}."
95
+
96
+ Example 5:
97
+ MESSAGE:
98
+ Here is a new skill for you -- computing the kth fibonacci number.
99
+
100
+ def fib(k: int) -> int:
101
+ if k < 0:
102
+ raise ValueError("k must be a non-negative integer")
103
+ elif k == 0:
104
+ return 0
105
+ elif k == 1:
106
+ return 1
107
+ a, b = 0, 1
108
+ for _ in range(2, k + 1):
109
+ a, b = b, a + b
110
+ return b
111
+
112
+ OUTPUT:
113
+ def fib(k: int) -> int:
114
+ """Compute and return the k-th Fibonacci number (0-indexed)."""
115
+ if k < 0:
116
+ raise ValueError("k must be a non-negative integer")
117
+ elif k == 0:
118
+ return 0
119
+ elif k == 1:
120
+ return 1
121
+ a, b = 0, 1
122
+ for _ in range(2, k + 1):
123
+ a, b = b, a + b
124
+ return b
125
+
126
+ Reminders:
127
+ - Output only the final, properly formatted Python class and function definitions, with no extra text or formatting.
128
+ - Add type annotations and docstrings wherever possible.
129
+ - Preserve and add comments as appropriate.
130
+ - If translation or correction is not possible, output the closest valid Python class/function definitions that fulfill the user's intent.
131
+ - Do not output any surrounding backticks (```) or code tags—output just the code directly.
132
+
133
+ This is the end of the classification instructions. Below is the input to process.
134
+
135
+ MESSAGE:
136
+ {message}
backend/frame/errands/message_code_variables.txt ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are an expert Python code and documentation analyst. Your task is to generate brief, intent-focused descriptions for all variables that are to be returned by the function defined in the provided CODE. Use the MESSAGE (natural language instructions and context) and the current TIDINGS (variable names and their existing descriptions) to inform your descriptions.
2
+
3
+ ===SEPARATOR===
4
+ You are an expert Python code and documentation analyst. Your task is to generate brief, intent-focused descriptions for all variables that are to be returned by the function defined in the provided CODE. Use the MESSAGE (natural language instructions and context) and the current TIDINGS (variable names and their existing descriptions) to inform your descriptions.
5
+
6
+ Guidelines:
7
+ - If a variable's description in TIDINGS is already sufficient and perhaps better than what you could generate, copy it exactly as is.
8
+ - Otherwise, write a new description that is brief, captures the type hints if possible, and describes the intent and role of the variable. Include other names if the MESSAGE gives any.
9
+ - The description should not be too long or too short; it should be just informative enough.
10
+ - Focus on the intent and role of each variable.
11
+
12
+ # Note: For each line in TIDINGS, the format is:
13
+ # variable_name: value # description
14
+ # Only use the description after the '#' for reference; ignore the value.
15
+
16
+ For each variable that will be returned by the function in CODE, output a mapping in the following format:
17
+
18
+ variable_name # description
19
+
20
+ Only include variables that are returned by the function. For each, either copy the description from TIDINGS (if sufficient) or write a new one as per the guidelines above.
21
+
22
+ Reminders:
23
+ - Output the descriptions in the form variable_name # description
24
+ - Use expert description but don't get too verbose
25
+ - Do not output any other text. No introductions, no surrounding text, no triple backticks or code tags to indicate the presence of code-like output. Output just the descriptions in the requested format. Do not output any other text.
26
+
27
+ Input:
28
+ MESSAGE:
29
+ {message}
30
+
31
+ CODE:
32
+ {code}
33
+
34
+ TIDINGS (variable: value # description):
35
+ {tidings}
backend/frame/errands/message_data_processing.txt ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant for extracting and preparing Python code that stores all data from a user message. Your task is to process the MESSAGE below, which may contain structured data (such as tables, lists, JSON, CSV, key-value pairs), natural language descriptions of data, or a combination of both. Your goal is to output only the final, directly executable Python code that stores all the data from the message into a single dictionary called __vars, using keys as close as possible to the names, headings, or labels found in the message. Do not output any explanatory text, comments, or formatting—output only the code.
2
+
3
+ ===SEPARATOR===
4
+ You are given a MESSAGE that may contain:
5
+ - Structured data (tables, lists, JSON, CSV, key-value pairs, etc.)
6
+ - Natural language descriptions of data (e.g., "Set the user's age to 32 and name to Alice.")
7
+ - A mix of code blocks, structured data, and surrounding text
8
+
9
+ Your job is to:
10
+ 1. Identify all data present in the message, whether in structured form (tables, lists, JSON, CSV, etc.), code, or described in natural language.
11
+ 2. For each piece of data, generate Python code that assigns the value to a key in the __vars dictionary. Use keys that are as close as possible to the names, headings, or labels in the message. If the data is in a table or list, use a suitable Python structure (list, dict, etc.) and preserve the structure as the value for the corresponding key.
12
+ 3. If the message contains multiple data items, create a separate key-value pair for each, using clear and descriptive keys.
13
+ 4. If the message contains code that assigns values to variables, extract only the assignments and store them as key-value pairs in __vars (ignore function definitions or unrelated code).
14
+ 5. If the message contains natural language descriptions of data, convert them into key-value pairs in __vars.
15
+ 6. Do not include any code for printing, returning, or processing the data—just the assignment to the __vars dictionary that stores the data.
16
+ 7. Do not include any explanatory text, comments, or formatting (such as markdown backticks) in your output—output just the Python code that stores the data in __vars.
17
+ 8. Note that putting data into strings where applicable might require using escape characters.
18
+ 9. Do not decompose variables into sub-variables. E.g. if it is clear that there is to be one variable called "data" or "instruction" etc.., do not create sub-variables that capture the contents of this variable. Naturally, do that only if it is clear from the writing of the message (or positioning of headings) that this is the case.
19
+
20
+ Examples:
21
+
22
+ Example 1:
23
+ MESSAGE:
24
+ Set the user's name to Alice and age to 32.
25
+
26
+ OUTPUT:
27
+ __vars = {
28
+ "user_name": "Alice",
29
+ "user_age": 32
30
+ }
31
+
32
+ Example 2:
33
+ MESSAGE:
34
+ {"temperature": 23.5, "humidity": 60}
35
+
36
+ OUTPUT:
37
+ __vars = {
38
+ "temperature": 23.5,
39
+ "humidity": 60
40
+ }
41
+
42
+ Example 3:
43
+ MESSAGE:
44
+ | Name | Score |
45
+ |-------|-------|
46
+ | Bob | 90 |
47
+ | Alice | 95 |
48
+
49
+ OUTPUT:
50
+ __vars = {
51
+ "names": ["Bob", "Alice"],
52
+ "scores": [90, 95],
53
+ "data": [
54
+ {"Name": "Bob", "Score": 90},
55
+ {"Name": "Alice", "Score": 95}
56
+ ]
57
+ }
58
+
59
+ Example 4:
60
+ MESSAGE:
61
+ user = {"id": 123, "name": "Charlie"}
62
+
63
+ OUTPUT:
64
+ __vars = {
65
+ "user": {"id": 123, "name": "Charlie"}
66
+ }
67
+
68
+ Example 5:
69
+ MESSAGE:
70
+ The list of items is: apple, banana, cherry.
71
+
72
+ OUTPUT:
73
+ __vars = {
74
+ "items": ["apple", "banana", "cherry"]
75
+ }
76
+
77
+ Example 6:
78
+ MESSAGE:
79
+ CHAPTER 1:
80
+ It was the wildest of days.
81
+ I woke up completely excited and ready for school, but ...
82
+
83
+ OUTPUT:
84
+ __vars = {
85
+ "chapter_1": """It was the wildest of days.\nI woke up completely excited and ready for school, but ...\n"""
86
+ }
87
+
88
+ Example 7:
89
+ MESSAGE:
90
+ INSTRUCTION: Read the text and familiarize yourself with the characters of the story.
91
+
92
+ OUTPUT:
93
+ __vars = {
94
+ "instruction": "Read the text and familiarize yourself with the characters of the story."
95
+ }
96
+
97
+ Example 8:
98
+ MESSAGE:
99
+ The following is the prompt to be used in later procedures.
100
+
101
+ PROMPT:
102
+ Produce a comprehensive report on the major Christian events that took place this year between the 20th and 22nd of April 2025.
103
+
104
+ OUTPUT:
105
+ __vars = {
106
+ "PROMPT": "Produce a comprehensive report on the major Christian events that took place this year between the 20th and 22nd of April 2025."
107
+ }
108
+
109
+ Example 9:
110
+ MESSAGE:
111
+ Testimony:
112
+ I arrived home at 9am. She was already lying dead in her bed. I could not do anything. I called the police at 9.07am. Then, I ...
113
+
114
+ OUTPUT:
115
+ __vars = {
116
+ "Testimony": """I arrived home at 9am. She was already lying dead in her bed. I could not do anything. I called the police at 9.07am. Then, I ..."""
117
+ }
118
+
119
+ Reminders:
120
+ - Output only the Python code that stores the data in the __vars dictionary, with no extra text, comments, or formatting.
121
+ - Use keys that are as close as possible to the names/headings in the message.
122
+ - If the data is structured, preserve the structure in the Python code as the value for the corresponding key in __vars.
123
+ - If the data is described in natural language, convert it to key-value pairs in __vars.
124
+ - Do not output any surrounding backticks (```) or code tags—output just the code directly.
125
+
126
+ This is the end of the instructions. Below is the input to process.
127
+
128
+ MESSAGE:
129
+ {message}
backend/frame/errands/message_generating_routine_call.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant for generating a Python function call snippet. Your task is to process the MESSAGE, the generated CODE (a function definition), and the TIDINGS (a set of variable names and values). Your goal is to output a single line of Python code that calls the function defined in CODE, passing the variable names from TIDINGS as arguments (not their values), and captures the returned generator. The function defined in CODE always returns a generator of dictionaries (Generator[dict[str, Any], None, None]).
2
+
3
+ ===SEPARATOR===
4
+ You are given:
5
+ - MESSAGE: the original user message (for context) that was used to produce CODE
6
+ - CODE: a Python function definition (already generated)
7
+ - TIDINGS: a set of variable names and values (as would be passed to the function), some of which might be useful to pass to CODE in order to accomplish the intent of code in MESSAGE
8
+
9
+ Your job is to:
10
+ 1. Output a single line of Python code that calls the function defined in CODE, passing the variable names from TIDINGS as named arguments (keyword arguments) where applicable/relevant/good fit.
11
+ 2. Capture the returned generator into a __generator variable, such as
12
+ __generator = function_from_CODE(var1=var1, var2=var2, ...)
13
+ where var1, var2, ... are the variable names from TIDINGS.
14
+ 3. Output only the Python code line, with no extra text, comments, or formatting.
15
+
16
+ If there are no tidings, call the function defined in CODE with no arguments:
17
+ __generator = function_from_CODE()
18
+
19
+ This is the end of the instructions. Below is the input to process.
20
+
21
+ MESSAGE:
22
+ {message}
23
+
24
+ CODE:
25
+ {code}
26
+
27
+ TIDINGS:
28
+ {tidings}
29
+
30
+ Reminders:
31
+ - Output just the function call to the functiond defined in CODE so that the intent of MESSAGE (from which CODE was generated) is fulfilled.
32
+ - To pass variables into the function call, use variable names described under TIDINGS.
33
+ - Do not output any other text. Do not output any surrounding backticks or code tags.
backend/frame/errands/message_generating_routine_processing.txt ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are an expert Python code generator for an advanced AI system. Your job is to convert a user's natural language routine instruction (MESSAGE) into a single pure Python function called 'code' that returns a generator.
2
+
3
+ ===SEPARATOR===
4
+
5
+ You are an expert Python code generator for an advanced AI system. Convert user's natural language routine instruction (MESSAGE) into a single pure Python function called 'code' that returns a generator.
6
+
7
+ You are provided with:
8
+ - MESSAGE: The user's instruction, which may be a single short description of intended functionality or a multi-step list (bullets, numbered steps, etc.).
9
+ - SKILLS: A list of available function signatures and docstrings you may call in your code.
10
+ - TIDINGS: A list of available variables (with their values and descriptions) you may use as arguments or modify.
11
+
12
+ Your function must:
13
+ - Be named 'code' and use type hints for all arguments and the return value.
14
+ - Accept as arguments any TIDINGS that are relevant to the MESSAGE.
15
+ - Accumulate all user-facing output in a string variable called '__output' (do NOT use print). At the end, return this string as the first element of a tuple.
16
+ - Return a generator that yields notification dictionaries, each of which has a "type" field of type string, and other fields that are up to any yield point of the code.
17
+ - Return a final notification dictionary of shape `{ "type": "__final", "output": output, and "modified_vars": modified_vars }` (output: str, modified_vars: dict[str, Any]), where 'output' is the accumulated output string, and 'modified_vars' is a dictionary of all variables that were modified by the function.
18
+ - Have a high-quality docstring describing its purpose, arguments, and return value.
19
+ - Use SKILLS as needed to accomplish the MESSAGE.
20
+ - If the MESSAGE is a multi-step routine (bullets, numbered steps, or multiple sentences), interleave each step as a comment, followed by the code that implements that step. If there are jumps or loops, use Python control flow (for/while/if) to simulate them, with comments explaining the logic.
21
+ - When in doubt, generate code that best accomplishes the user's intent.
22
+ - Do not use print statements; yield a notification when asked to send a message/notification or to notify the user. An instruction such as "print hello world" should be interpreted as `yield {"type": "__output", "content": "hello world" }`.
23
+ - Modify variables that are listed in TIDINGS when referred to directly in natural language
24
+ - Feel free to create new variables when the MESSAGE instructs to create new ones.
25
+ - Sometimes you might want to create auxiliary/temporary variables -- i.e. variables that are not really mentioned by the user directly or indirectly. When that's the case, please prefix them by `__` (double underscore).
26
+ - If the MESSAGE contains a payload text (for example, separated by a heading or clearly not an instruction), store that text without modification in a new variable with an appropriate name and a comment explaining its purpose. Do not process or change the payload text; just store it. There can be multiple payload texts and the payloads can be strings, binary/hex strings, integers, floats, hashes, etc..
27
+
28
+ Example input:
29
+ MESSAGE:
30
+ """
31
+ 1. Greet the user by name.
32
+ 2. Add 10 to their score.
33
+ 3. If their score is above 100, congratulate them.
34
+ """
35
+ SKILLS:
36
+ - greet_user(name: str) -> str: Returns a greeting for the user.
37
+ TIDINGS:
38
+ - name = Alice # The user's name
39
+ - score = 95 # The user's current score
40
+
41
+ Example output:
42
+ ```python
43
+ def code(name: str, score: int) -> Generator[dict[str, Any], None, None]:
44
+ """
45
+ Greets the user, updates their score, and congratulates them if their score exceeds 100.
46
+ Args:
47
+ name: The user's name.
48
+ score: The user's current score.
49
+ Returns:
50
+ A tuple containing the output string and a dictionary of modified variables.
51
+ """
52
+ # 1. Greet the user by name.
53
+ yield { "type": "__output", "content": greet_user(name) }
54
+ # 2. Add 10 to their score.
55
+ score += 10
56
+ # 3. If their score is above 100, congratulate them.
57
+ if score > 100:
58
+ yield { "type": "__output", "content": ff"Congratulations, {name}! Your score is {score}.\n" }
59
+ __vars = {'score': score}
60
+ yield { "type": "__final", "output": None, and "modified_vars": __vars }
61
+ ```
62
+
63
+ Example input:
64
+ MESSAGE:
65
+ """
66
+ 1. For each value in the list, compute its square root and add it to a running total.
67
+ 2. If the total exceeds 100, output a warning message.
68
+ 3. Output the final total.
69
+ """
70
+ SKILLS:
71
+ - format_warning(msg: str) -> str: Formats a warning message for the user.
72
+ TIDINGS:
73
+ - values = [4, 16, 25, 36, 49] # List of numbers
74
+ - total = 0 # Running total
75
+
76
+ Example output:
77
+ ```python
78
+ def code(values: list[float], total: float) -> Generator[dict[str, Any], None, None]:
79
+ """
80
+ Computes the square root of each value, adds to total, warns if total > 100, and outputs the total.
81
+ Args:
82
+ values: List of numbers to process.
83
+ total: Running total.
84
+ Returns:
85
+ A tuple containing the output string and a dictionary of modified variables.
86
+ """
87
+ import math
88
+ __output = ""
89
+ # 1. For each value in the list, compute its square root and add it to a running total.
90
+ for __v in values:
91
+ __sqrt = math.sqrt(__v)
92
+ total += __sqrt
93
+ # 2. If the total exceeds 100, output a warning message.
94
+ if total > 100:
95
+ yield { "type": "__output", "content": format_warning(f"Total exceeded 100: {total}") + "\n" }
96
+ # 3. Output the final total.
97
+ yield { "type": "__output", "content": f"Final total: {total}\n" }
98
+ __vars = { 'total': total }
99
+ yield { "type": "__final", "output": None, and "modified_vars": __vars }
100
+ ```
101
+
102
+ Example input:
103
+ MESSAGE:
104
+ """
105
+ 1. Check if the user is an admin.
106
+ 2. If so, grant them access by sending a message of type "access_granted" and an accompanying description, and log the event.
107
+ 3. Otherwise, deny access by sending a message of type "access_denied" and an accompanying description, and log the attempt.
108
+ """
109
+ SKILLS:
110
+ - log_event(event: str) -> None: Logs an event string.
111
+ - grant_access(user: str) -> str: Grants access to the user and returns a confirmation message.
112
+ - deny_access(user: str) -> str: Denies access to the user and returns a denial message.
113
+ TIDINGS:
114
+ - user = bob # The username
115
+ - is_admin = False # Whether the user is an admin
116
+
117
+ Example output:
118
+ ```python
119
+ def code(user: str, is_admin: bool) -> Generator[dict[str, Any], None, None]:
120
+ """
121
+ Checks admin status, grants or denies access, and logs the event.
122
+ Args:
123
+ user: The username.
124
+ is_admin: Whether the user is an admin.
125
+ Returns:
126
+ A tuple containing the output string and a dictionary of modified variables.
127
+ """
128
+ __output = ""
129
+ # 1. Check if the user is an admin.
130
+ if is_admin:
131
+ # 2. If so, grant them access and log the event.
132
+ yield { "type": "access_granted", "description": grant_access(user) + "\n" }
133
+ log_event(f"Access granted to {user}")
134
+ else:
135
+ # 3. Otherwise, deny access and log the attempt.
136
+ yield { "type": "access_denied", "description": deny_access(user) + "\n" }
137
+ log_event(f"Access denied to {user}")
138
+ __vars = {}
139
+ yield { "type": "__final", "output": None, and "modified_vars": __vars }
140
+ ```
141
+
142
+ Reminders:
143
+ - Output a function called `code` relying on SKILLS and TIDINGS where needed
144
+ - The function should be a pure function -- do not access global variables.
145
+ - Prefer the functions described in SKILLS over external libraries
146
+ - Output just the code of the function. Do not output any other text.
147
+
148
+ Now, generate the function as described above for the given MESSAGE, SKILLS, and TIDINGS.
149
+
150
+ SKILLS:
151
+ {skills}
152
+
153
+ TIDINGS:
154
+ {tidings}
155
+
156
+ MESSAGE:
157
+ {message}
158
+
159
+ Reminders:
160
+ - Output a function called `code` relying on SKILLS and TIDINGS where needed
161
+ - The function should be a pure function -- do not access global variables.
162
+ - Prefer the functions described in SKILLS over external libraries
163
+ - Output just the code of the function. Do not output any other text.
164
+ - Remember to return a generator of dictionaries, and to use yield to report user intermediate messages.
backend/frame/errands/message_routine_call.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant for generating a Python function call snippet. Your task is to process the MESSAGE, the generated CODE (a function definition), and the TIDINGS (a set of variable names and values). Your goal is to output a single line of Python code that calls the function defined in CODE, passing the variable names from TIDINGS as arguments (not their values), and captures the return values into variables named __output and __vars. The function defined in CODE always returns a tuple (output: str, variables: dict[str, Any]).
2
+
3
+ ===SEPARATOR===
4
+ You are given:
5
+ - MESSAGE: the original user message (for context) that was used to produce CODE
6
+ - CODE: a Python function definition (already generated)
7
+ - TIDINGS: a set of variable names and values (as would be passed to the function), some of which might be useful to pass to CODE in order to accomplish the intent of code in MESSAGE
8
+
9
+ Your job is to:
10
+ 1. Output a single line of Python code that calls the function defined in CODE, passing the variable names from TIDINGS as named arguments (keyword arguments) where applicable/relevant/good fit.
11
+ 2. Capture the return values into variables named __output and __vars, like this:
12
+ __output, __vars = function_from_CODE(var1=var1, var2=var2, ...)
13
+ where var1, var2, ... are the variable names from TIDINGS.
14
+ 3. Output only the Python code line, with no extra text, comments, or formatting.
15
+
16
+ If there are no tidings, call the function defined in CODE with no arguments:
17
+ __output, __vars = function_from_CODE()
18
+
19
+ This is the end of the instructions. Below is the input to process.
20
+
21
+ MESSAGE:
22
+ {message}
23
+
24
+ CODE:
25
+ {code}
26
+
27
+ TIDINGS:
28
+ {tidings}
29
+
30
+ Reminders:
31
+ - Output just the function call to the functiond defined in CODE so that the intent of MESSAGE (from which CODE was generated) is fulfilled.
32
+ - To pass variables into the function call, use variable names described under TIDINGS.
33
+ - Do not output any other text. Do not output any surrounding backticks or code tags.
backend/frame/errands/message_routine_processing.txt ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are an expert Python code generator for an advanced AI system. Your job is to convert a user's natural language routine instruction (MESSAGE) into a single pure Python function called 'code'.
2
+
3
+ ===SEPARATOR===
4
+
5
+ You are an expert Python code generator for an advanced AI system. Convert user's natural language routine instruction (MESSAGE) into a single pure Python function called 'code'.
6
+
7
+ You are provided with:
8
+ - MESSAGE: The user's instruction, which may be a single short description of intended functionality or a multi-step list (bullets, numbered steps, etc.).
9
+ - SKILLS: A list of available function signatures and docstrings you may call in your code.
10
+ - TIDINGS: A list of available variables (with their values and descriptions) you may use as arguments or modify.
11
+
12
+ Your function must:
13
+ - Be named 'code' and use type hints for all arguments and the return value.
14
+ - Accept as arguments any TIDINGS that are relevant to the MESSAGE.
15
+ - Accumulate all user-facing output in a string variable called '__output' (do NOT use print). At the end, return this string as the first element of a tuple.
16
+ - Return a tuple (output: str, modified_vars: dict[str, Any]), where 'output' is the accumulated output string, and 'modified_vars' is a dictionary of all variables that were modified by the function.
17
+ - Have a high-quality docstring describing its purpose, arguments, and return value.
18
+ - Use SKILLS as needed to accomplish the MESSAGE.
19
+ - If the MESSAGE is a multi-step routine (bullets, numbered steps, or multiple sentences), interleave each step as a comment, followed by the code that implements that step. If there are jumps or loops, use Python control flow (for/while/if) to simulate them, with comments explaining the logic.
20
+ - When in doubt, generate code that best accomplishes the user's intent.
21
+ - Do not use print statements; all output must be accumulated in '__output'.
22
+ - Modify variables that are listed in TIDINGS when referred to directly in natural language
23
+ - Feel free to create new variables when the MESSAGE instructs to create new ones.
24
+ - Sometimes you might want to create auxiliary/temporary variables. When that's the case, please prefix them by `__` (double underscore).
25
+ - If the MESSAGE contains a payload text (for example, separated by a heading or clearly not an instruction), store that text without modification in a new variable with an appropriate name and a comment explaining its purpose. Do not process or change the payload text; just store it. There can be multiple payload texts and the payloads can be strings, binary/hex strings, integers, floats, hashes, etc..
26
+
27
+ Example input:
28
+ MESSAGE:
29
+ """
30
+ 1. Greet the user by name.
31
+ 2. Add 10 to their score.
32
+ 3. If their score is above 100, congratulate them.
33
+ """
34
+ SKILLS:
35
+ - greet_user(name: str) -> str: Returns a greeting for the user.
36
+ TIDINGS:
37
+ - name = Alice # The user's name
38
+ - score = 95 # The user's current score
39
+
40
+ Example output:
41
+ ```python
42
+ def code(name: str, score: int) -> tuple[str, dict[str, Any]]:
43
+ """
44
+ Greets the user, updates their score, and congratulates them if their score exceeds 100.
45
+ Args:
46
+ name: The user's name.
47
+ score: The user's current score.
48
+ Returns:
49
+ A tuple containing the output string and a dictionary of modified variables.
50
+ """
51
+ __output = ""
52
+ # 1. Greet the user by name.
53
+ __output += greet_user(name) + "\n"
54
+ # 2. Add 10 to their score.
55
+ score += 10
56
+ # 3. If their score is above 100, congratulate them.
57
+ if score > 100:
58
+ __output += f"Congratulations, {name}! Your score is {score}.\n"
59
+ __vars = {'score': score}
60
+ return __output, __vars
61
+ ```
62
+
63
+ Example input:
64
+ MESSAGE:
65
+ """
66
+ 1. For each value in the list, compute its square root and add it to a running total.
67
+ 2. If the total exceeds 100, output a warning message.
68
+ 3. Output the final total.
69
+ """
70
+ SKILLS:
71
+ - format_warning(msg: str) -> str: Formats a warning message for the user.
72
+ TIDINGS:
73
+ - values = [4, 16, 25, 36, 49] # List of numbers
74
+ - total = 0 # Running total
75
+
76
+ Example output:
77
+ ```python
78
+ def code(values: list[float], total: float) -> tuple[str, dict[str, Any]]:
79
+ """
80
+ Computes the square root of each value, adds to total, warns if total > 100, and outputs the total.
81
+ Args:
82
+ values: List of numbers to process.
83
+ total: Running total.
84
+ Returns:
85
+ A tuple containing the output string and a dictionary of modified variables.
86
+ """
87
+ import math
88
+ __output = ""
89
+ # 1. For each value in the list, compute its square root and add it to a running total.
90
+ for __v in values:
91
+ __sqrt = math.sqrt(__v)
92
+ total += __sqrt
93
+ # 2. If the total exceeds 100, output a warning message.
94
+ if total > 100:
95
+ __output += format_warning(f"Total exceeded 100: {total}") + "\n"
96
+ # 3. Output the final total.
97
+ __output += f"Final total: {total}\n"
98
+ __vars = {'total': total}
99
+ return __output, __vars
100
+ ```
101
+
102
+ Example input:
103
+ MESSAGE:
104
+ """
105
+ 1. Check if the user is an admin.
106
+ 2. If so, grant them access and log the event.
107
+ 3. Otherwise, deny access and log the attempt.
108
+ """
109
+ SKILLS:
110
+ - log_event(event: str) -> None: Logs an event string.
111
+ - grant_access(user: str) -> str: Grants access to the user and returns a confirmation message.
112
+ - deny_access(user: str) -> str: Denies access to the user and returns a denial message.
113
+ TIDINGS:
114
+ - user = bob # The username
115
+ - is_admin = False # Whether the user is an admin
116
+
117
+ Example output:
118
+ ```python
119
+ def code(user: str, is_admin: bool) -> tuple[str, dict[str, Any]]:
120
+ """
121
+ Checks admin status, grants or denies access, and logs the event.
122
+ Args:
123
+ user: The username.
124
+ is_admin: Whether the user is an admin.
125
+ Returns:
126
+ A tuple containing the output string and a dictionary of modified variables.
127
+ """
128
+ __output = ""
129
+ # 1. Check if the user is an admin.
130
+ if is_admin:
131
+ # 2. If so, grant them access and log the event.
132
+ __output += grant_access(user) + "\n"
133
+ log_event(f"Access granted to {user}")
134
+ else:
135
+ # 3. Otherwise, deny access and log the attempt.
136
+ __output += deny_access(user) + "\n"
137
+ log_event(f"Access denied to {user}")
138
+ __vars = {}
139
+ return __output, __vars
140
+ ```
141
+
142
+ Reminders:
143
+ - Output a function called `code` relying on SKILLS and TIDINGS where needed
144
+ - The function should be a pure function -- do not access global variables.
145
+ - Prefer the functions described in SKILLS over external libraries
146
+ - Output just the code of the function Do not output any other text.
147
+
148
+ Now, generate the function as described above for the given MESSAGE, SKILLS, and TIDINGS.
149
+
150
+ SKILLS:
151
+ {skills}
152
+
153
+ TIDINGS:
154
+ {tidings}
155
+
156
+ MESSAGE:
157
+ {message}
158
+
159
+ Reminders:
160
+ - Output a function called `code` relying on SKILLS and TIDINGS where needed
161
+ - The function should be a pure function -- do not access global variables.
162
+ - Prefer the functions described in SKILLS over external libraries; these functions are available in the global scope and do not require imports.
163
+ - Output just the code of the function Do not output any other text.
164
+ - Remember to return two outputs: one for the aggregated function output, and one that is a dictionary of all variables that were modified by the function.
backend/frame/errands/message_routine_variables.txt ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are an expert Python code and documentation analyst. Your task is to generate brief, intent-focused descriptions for all variables that are to be returned by the function defined in the provided CODE. Use the MESSAGE (natural language instructions and context) and the current TIDINGS (variable names and their existing descriptions) to inform your descriptions.
2
+
3
+ ===SEPARATOR===
4
+
5
+ You are an expert Python code and documentation analyst. Your task is to generate brief, intent-focused descriptions for all variables that are to be returned by the function defined in the provided CODE. Use the MESSAGE (natural language instructions and context) and the current TIDINGS (variable names and their existing descriptions) to inform your descriptions.
6
+
7
+ Guidelines:
8
+ - If a variable's description in TIDINGS is already sufficient and perhaps better than what you could generate, copy it exactly as is.
9
+ - Otherwise, write a new description that is brief, captures the type hints if possible, and describes the intent and role of the variable. Include other names if the MESSAGE gives any.
10
+ - The description should not be too long or too short; it should be just informative enough.
11
+ - Focus on the intent and role of each variable.
12
+
13
+ # Note: For each line in TIDINGS, the format is:
14
+ # variable_name: value # description
15
+ # Only use the description after the '#' for reference; ignore the value.
16
+
17
+ For each variable that will be returned by the function in CODE, output a mapping in the following format:
18
+
19
+ variable_name # description
20
+
21
+ Only include variables that are returned by the function. For each, either copy the description from TIDINGS (if sufficient) or write a new one as per the guidelines above.
22
+
23
+ Reminders:
24
+ - Output the descriptions in the form variable_name # description
25
+ - Use expert description but don't get too verbose
26
+ - Do not output any other text. No introductions, no surrounding text, no triple backticks or code tags to indicate the presence of code-like output. Output just the descriptions in the requested format. Do not output any other text.
27
+
28
+ Input:
29
+ MESSAGE:
30
+ {message}
31
+
32
+ CODE:
33
+ {code}
34
+
35
+ TIDINGS (variable: value # description):
36
+ {tidings}
backend/frame/errands/message_type.txt ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful assistant for detecting the type of the message send by the user. Based on the contents of and instructions in the MESSAGE, respond with one of the following message types: code, code_skill, routine, routine_skill, query, query_skill, data. Do not output any other text.
2
+
3
+ ===SEPARATOR===
4
+ You are deciding how the contents of MESSAGE will be handled by an AI system. To do so, choose on of the following TYPES that best characterizes the nature of the message. Every message can have only one type. Do not output any other text.
5
+
6
+ TYPES:
7
+ code
8
+ ---
9
+ code is the type of messages that contain code that is to be directly executed. The entire message can be
10
+ - a directly pasted code snippet which is not just function definitions; or
11
+ - it can be a human-friendly message with the code section to be executed enclosed in markdown single/triple backticks (e.g. `my one-line code` or ```my code```) or some other obvious separators (e.g. <code></code>). In this case, the surrounding human instructions must indicate that this code is to be executed rather than stored as a skill. Also, the surrounding instructions (that is, instructions that are outside obviously marked code blocks) might describe some further circumstances of the code. There can be more than one code block in the message, but the idea is that all the code blocks can be concatenated and executed as one, give or take some small modifications due to surrounding instructions.
12
+ Note that messages asking to generate code (even when including examples) are not of type code but either query or query skill.
13
+ Note that messages that ask the AI system to store some function for future use, or that are not accompanied by surrounding natural language instruction but are just definitions rather than actionable code are not of type code but likely of type code_skill. Simply put, if your message is just a function definition, it's a code_skill, not code.
14
+ Note that messages that provide only pseudo-code that needs to be processed into executable Python code are not of type code but likely of type routine or routine_skill.
15
+
16
+ code_skill
17
+ ---
18
+ code_skill is the type of messages that contain code that is to be stored as a future functio(s), skill(s), or tool(s). The entire message can be
19
+ - a directly pasted code snippet which contains only definitions of a single function or multiple functions, classes, etc.. but not statements
20
+ - a human-friendly message with the code section(s) that contain(s) definitions enclosed in markdown triple backticks or some other obvious separators (e.g. ```def my_function()...``` or ```<code></code>```). In this case, the surrounding human instructions can also explicitly hint that this code is to be stored as function(s) for future use rather than code that is to be directly executed.
21
+ Note that messages asking to genrate code (even when including examples) are not of type code_skill but either query or query skill.
22
+ Note that messages that ask the AI system to execute code directly to alter the application state are not of type code_skill but likely code.
23
+ Note that messages that provide only pseudo-code or natural language instructions are not of type code_skill but likely of type routine or routine_skill.
24
+
25
+ routine
26
+ ---
27
+ routine is the tupe of messages that contain natural language that instructs the model to perform some operation(s) that would be better converted into code and executed. The entire message can be either a single instructions that can be converted to code and executed for producing a provably correct result, or a list of instructions (loosely formatted, a sequence of sentences, or a markdown-formatted list).
28
+ Note that while there might be some hints of code in the message, messages containing mostly code and only a few surrounding instructions are likely not of type routine but more likely code or code_skill.
29
+ Note that messages that ask the AI system to store some skill for future use on some parameters to be given in the future are not of type routine but likely routine_skill.
30
+ Note thet messages that sound like a request at a language model to perform some operation (i.e. they sound like a prompt oriented at some semantic operation or an operation that cannot be easily converted into code) are not of type skill byt likely of type quer or query_skill.
31
+
32
+ routine_skill
33
+ ---
34
+ routine_skill is the type of messages that describe operations or workflows in natural language that are not to be immediately executed, but rather saved as reusable routines to be invoked later. The message often uses future-oriented or generalizing language (e.g., "In the future, when I say X, do Y" or "remember this for the future"), or describes a behavior the AI should learn or adopt for future use. The instructions may be procedural (i.e., can be translated into code) but the key distinction from routine is the intent to store or remember the behavior, not execute it right now. These routines might include steps that require parameters to be filled in during later invocations.
35
+
36
+ Note that messages that describe natural language steps to perform a one-off operation (i.e. not to be stored) are not of type routine_skill but of type routine.
37
+ Note that if the message contains mostly code and is about storing function definitions, it is not a routine_skill but more likely a code_skill.
38
+ Note that vague, open-ended, or semantic tasks that are not clearly procedural are not of type routine_skill but more likely query_skill.
39
+
40
+ query
41
+ ---
42
+ query is the type of messages that ask a one-off question, request, or task from the AI that does not require code execution or skill storage. These messages typically resemble a standard prompt to a language model and might request explanations, summaries, answers, creative writing, or similar outputs. They do not require storing skills for later or executing code—just generating a response.
43
+
44
+ Note that queries may sometimes mention code, examples, or functions, but if the purpose is to ask for help, explanation, or generation (rather than execution or storage), the message is a query.
45
+ Note that if the query is asking the AI to create a reusable behavior, it may be query_skill instead.
46
+ Note that if the query is better expressed as a step-by-step procedural task to be executed, it may be routine instead.
47
+
48
+ query_skill
49
+ ---
50
+ query_skill is the type of messages that instruct the AI to create a reusable capability or skill that handles a kind of query. These messages often generalize a task into a reusable behavior (e.g., “Whenever I ask you to summarize a paper, follow this method or use the following prompts verbatim”). The emphasis is on defining a way for the AI to handle a category of future inputs, rather than simply responding to one instance.
51
+
52
+ Note that query_skill messages are not asking for execution right now but are defining a behavior to be remembered and reused.
53
+ Note that if the message only asks a single question, or provides input for a one-time output, it is not a query_skill but likely a query.
54
+ Note that if the instruction is better treated as code or procedural logic for execution, it is not a query_skill but more likely routine_skill or code_skill.
55
+
56
+ data
57
+ ---
58
+ data is the type of messages that provide structured or semi-structured input (e.g., tables, lists, JSON, CSV, key-value pairs, raw text samples) meant to be stored, parsed, processed, or used as input by a previously defined function, routine, or skill. These messages typically do not contain executable instructions or requests, but are instead meant to supply information or parameters.
59
+
60
+ The message may contain:
61
+ - JSON objects or arrays;
62
+ - CSV-formatted rows;
63
+ - tables (e.g., markdown-style or plain text with consistent delimiters);
64
+ - raw text samples or transcripts;
65
+ - named parameters or inputs for a function;
66
+ or any other structured information that can be interpreted as input data.
67
+
68
+ Note that if the message includes a request or prompt alongside the data, it may be better classified as a query or routine.
69
+ Note that if the message includes code or instructions for what to do with the data, it may be code, code_skill, routine, or routine_skill depending on the intent.
70
+ Note that data does not include natural language instructions but only the content meant to be processed or consumed.
71
+
72
+
73
+ This is the end of the classification instructions. Below is the input to classify.
74
+
75
+ Reminders:
76
+ - Output the type among TYPES that best characterizes the nature of the message given.
77
+ - Output just one of the TYPES (code, code_skill, routine, routine_skill, query, query_skill, data).
78
+ - Do not output any other text.
79
+
80
+ MESSAGE:
81
+ {message}
backend/frame/errands4.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import re
16
+
17
+ from frame.clients import Client
18
+
19
+
20
+ def namify_model_path(hf_model_path: str) -> str:
21
+ return hf_model_path.replace("/", "_").replace("-", "_").replace(" ", "_")
22
+
23
+
24
+ class Errand:
25
+ def __init__(self, pre_prompt: str, prompt: str) -> None:
26
+ self.pre_prompt = pre_prompt
27
+ self.prompt = prompt
28
+
29
+ def run(self, runner: Client, args: dict, completion_config: dict = {}) -> str:
30
+ prompt_with_args = self.prompt
31
+ for arg_name, arg_value in args.items():
32
+ prompt_with_args = prompt_with_args.replace("{" + arg_name + "}", arg_value)
33
+
34
+ ret = runner.run(self.pre_prompt, prompt_with_args, completion_config)
35
+ return ret
36
+
37
+
38
+ class FileErrand(
39
+ Errand
40
+ ): # NOTE: this is only for easier debug for now; production code should use errands directly
41
+ def __init__(self, errand_path: str) -> None:
42
+ with open(errand_path, "r") as f:
43
+ errand = f.read()
44
+
45
+ errand_parts = errand.split("===SEPARATOR===")
46
+ if len(errand_parts) != 2:
47
+ raise ValueError(
48
+ f"Errand file {errand_path} is not valid. It should contain exactly one separator."
49
+ )
50
+ pre_prompt = errand_parts[0].strip()
51
+ prompt = errand_parts[1].strip()
52
+ super().__init__(pre_prompt, prompt)
53
+
54
+
55
+ class MultipleChoiceErrand(FileErrand):
56
+ def __init__(self, errand_path: str, choices: list[str]) -> None:
57
+ super().__init__(errand_path)
58
+ self.choices = choices
59
+
60
+ def run(
61
+ self, runner: Client, args: dict, completion_config: dict = {}
62
+ ) -> str | None:
63
+ raw_completion = super().run(runner, args, completion_config)
64
+
65
+ for choice in self.choices:
66
+ if choice in raw_completion:
67
+ return choice
68
+
69
+ return None
70
+
71
+
72
+ import os
73
+ from pathlib import Path
74
+
75
+
76
+ class MessageTypeErrand(MultipleChoiceErrand):
77
+ def __init__(self) -> None:
78
+ errand_path = os.path.join(
79
+ Path(__file__).resolve().parent, "errands/message_type.txt"
80
+ )
81
+ choices = [
82
+ "code_skill",
83
+ "routine_skill",
84
+ "query_skill",
85
+ "code",
86
+ "routine",
87
+ "query",
88
+ "data",
89
+ ]
90
+ super().__init__(errand_path, choices)
91
+
92
+
93
+ class MessageCodeProcessingErrand(FileErrand):
94
+ def __init__(self) -> None:
95
+ errand_path = os.path.join(
96
+ Path(__file__).resolve().parent, "errands/message_code_processing.txt"
97
+ )
98
+ super().__init__(errand_path)
99
+
100
+
101
+ class MessageCodeSkillProcessingErrand(FileErrand):
102
+ def __init__(self) -> None:
103
+ errand_path = os.path.join(
104
+ Path(__file__).resolve().parent, "errands/message_code_skill_processing.txt"
105
+ )
106
+ super().__init__(errand_path)
107
+
108
+
109
+ class MessageCodeCallErrand(FileErrand):
110
+ def __init__(self) -> None:
111
+ errand_path = os.path.join(
112
+ Path(__file__).resolve().parent, "errands/message_code_call.txt"
113
+ )
114
+ super().__init__(errand_path)
115
+
116
+
117
+ class MessageCodeVariablesErrand(FileErrand):
118
+ def __init__(self) -> None:
119
+ errand_path = os.path.join(
120
+ Path(__file__).resolve().parent, "errands/message_code_variables.txt"
121
+ )
122
+ super().__init__(errand_path)
123
+
124
+
125
+ class MessageRoutineProcessingErrand(FileErrand):
126
+ def __init__(self) -> None:
127
+ errand_path = os.path.join(
128
+ Path(__file__).resolve().parent, "errands/message_routine_processing.txt"
129
+ )
130
+ super().__init__(errand_path)
131
+
132
+
133
+ class MessageGeneratingRoutineProcessingErrand(FileErrand):
134
+ def __init__(self) -> None:
135
+ errand_path = os.path.join(
136
+ Path(__file__).resolve().parent,
137
+ "errands/message_generating_routine_processing.txt",
138
+ )
139
+ super().__init__(errand_path)
140
+
141
+
142
+ class MessageRoutineCallErrand(FileErrand):
143
+ def __init__(self) -> None:
144
+ errand_path = os.path.join(
145
+ Path(__file__).resolve().parent, "errands/message_routine_call.txt"
146
+ )
147
+ super().__init__(errand_path)
148
+
149
+
150
+ class MessageGeneratingRoutineCallErrand(FileErrand):
151
+ def __init__(self) -> None:
152
+ errand_path = os.path.join(
153
+ Path(__file__).resolve().parent,
154
+ "errands/message_generating_routine_call.txt",
155
+ )
156
+ super().__init__(errand_path)
157
+
158
+
159
+ class MessageRoutineVariablesErrand(FileErrand):
160
+ def __init__(self) -> None:
161
+ errand_path = os.path.join(
162
+ Path(__file__).resolve().parent, "errands/message_routine_variables.txt"
163
+ )
164
+ super().__init__(errand_path)
165
+
166
+
167
+ class MessageDataProcessingErrand(FileErrand):
168
+ def __init__(self) -> None:
169
+ errand_path = os.path.join(
170
+ Path(__file__).resolve().parent, "errands/message_data_processing.txt"
171
+ )
172
+ super().__init__(errand_path)
173
+
174
+
175
+ default_errand_profile: dict[str, type[Errand] | None] = {
176
+ "message_type": MessageTypeErrand,
177
+ "message_code_processing": MessageCodeProcessingErrand,
178
+ "message_code_skill_processing": MessageCodeSkillProcessingErrand,
179
+ "message_code_call": MessageCodeCallErrand,
180
+ "message_code_variables": MessageCodeVariablesErrand,
181
+ "message_routine_processing": MessageRoutineProcessingErrand,
182
+ "message_generating_routine_processing": MessageGeneratingRoutineProcessingErrand,
183
+ "message_routine_call": MessageRoutineCallErrand,
184
+ "message_generating_routine_call": MessageGeneratingRoutineCallErrand,
185
+ "message_routine_variables": MessageRoutineVariablesErrand,
186
+ "message_data_processing": MessageDataProcessingErrand,
187
+ "language_model": None, # on purpose, currently there is no errand but there might be a client profile # TODO: double-check if this is still needed in V4
188
+ }
backend/frame/harness4.py ADDED
@@ -0,0 +1,775 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import ast
16
+ from dataclasses import dataclass
17
+ from datetime import datetime
18
+ from typing import Any, Generator
19
+
20
+ from frame.clients import *
21
+ from frame.errands4 import Errand, default_errand_profile
22
+ from frame.routines import *
23
+ from frame.tidings import *
24
+
25
+
26
+ @dataclass
27
+ class FrameConfigV4:
28
+ long_context_cutoff: int = 8192
29
+ force_long_context: bool = False
30
+ max_iterations: int = 1024
31
+ interaction_level: str = "none" # one of "none", "system", "lm"
32
+
33
+ def update(self, **kwargs) -> None:
34
+ for key, value in kwargs.items():
35
+ if hasattr(self, key):
36
+ setattr(self, key, value)
37
+
38
+ def to_dict(self) -> dict:
39
+ return {
40
+ "long_context_cutoff": self.long_context_cutoff,
41
+ "force_long_context": self.force_long_context,
42
+ "max_iterations": self.max_iterations,
43
+ }
44
+
45
+ def from_dict(self, config: dict) -> None:
46
+ self.update(**config)
47
+
48
+
49
+ class SkillV4:
50
+ name: str
51
+ source_message: str
52
+
53
+ python_name: str
54
+ docstring: str
55
+ code: str
56
+
57
+ def __init__(
58
+ self,
59
+ name: str,
60
+ source_message: str,
61
+ python_name: str,
62
+ docstring: str,
63
+ code: str,
64
+ ) -> None:
65
+ self.name = name
66
+ self.python_name = python_name
67
+ self.docstring = docstring
68
+ self.code = code
69
+ self.source_message = source_message
70
+
71
+ def to_dict(self) -> dict:
72
+ return {
73
+ "name": self.name,
74
+ "python_name": self.python_name,
75
+ "docstring": self.docstring,
76
+ "code": self.code,
77
+ "source_message": self.source_message,
78
+ }
79
+
80
+ def from_dict(self, skill: dict) -> None:
81
+ self.name = skill["name"]
82
+ self.python_name = skill["python_name"]
83
+ self.docstring = skill["docstring"]
84
+ self.code = skill["code"]
85
+ self.source_message = skill["source_message"]
86
+
87
+ def __str__(self) -> str:
88
+ return f"Skill: {self.name}\nPython name: {self.python_name}\nDocstring: {self.docstring}\nCode: {self.code}\nSource message: {self.source_message}"
89
+
90
+
91
+ class FrameV4:
92
+ # general
93
+ instance_id: str
94
+ config: FrameConfigV4
95
+ client_profile: dict[str, Client]
96
+ errand_profile: dict[str, type[Errand]]
97
+ compilation_trace: Trace
98
+ execution_trace: Trace
99
+ max_iterations: int = 1024
100
+
101
+ # skills
102
+ skills: dict[str, SkillV4]
103
+
104
+ # context
105
+ tidings: dict[str, Tiding]
106
+ globals: dict[str, Any]
107
+ last_mid: int
108
+
109
+ def __init__(
110
+ self,
111
+ client_profile: dict[str, Client] | Client,
112
+ instance_id: str | None = None,
113
+ config: FrameConfigV4 | None = None,
114
+ errand_profile: dict[str, type[Errand]] | None = None,
115
+ compilation_trace: bool | str | Trace | None = None,
116
+ execution_trace: bool | str | Trace | None = None,
117
+ ) -> None:
118
+ self.instance_id = (
119
+ datetime.now().strftime("%Y%m%d_%H-%M-%S")
120
+ if instance_id is None
121
+ else instance_id
122
+ )
123
+ self.config = FrameConfigV4() if config is None else config
124
+
125
+ self.errand_profile = (
126
+ {**default_errand_profile, **errand_profile}
127
+ if errand_profile is not None
128
+ else {**default_errand_profile}
129
+ )
130
+ if isinstance(client_profile, Client):
131
+ self.client_profile = {
132
+ k: client_profile for k, _ in self.errand_profile.items()
133
+ }
134
+ elif isinstance(client_profile, dict):
135
+ self.client_profile = client_profile
136
+ else:
137
+ raise ValueError(f"Invalid client profile type: {type(client_profile)}")
138
+
139
+ self.compilation_trace = self.make_trace_from_arg(
140
+ path=f"./logs/{self.instance_id}_compilation.log",
141
+ trace_arg=compilation_trace,
142
+ )
143
+ self.execution_trace = self.make_trace_from_arg(
144
+ path=f"./logs/{self.instance_id}_execution.log", trace_arg=execution_trace
145
+ )
146
+
147
+ self.new_instance()
148
+
149
+ def get_chat_context_dict(self) -> dict:
150
+ """
151
+ Returns the chat context as a dictionary
152
+ """
153
+
154
+ filtered_system_globals = {
155
+ k: v
156
+ for k, v in self.globals.items()
157
+ if not k.startswith("__")
158
+ and not callable(v)
159
+ and type(v) in [float, int, str, bool, list, set, dict, tuple]
160
+ }
161
+ return {
162
+ "mid": self.last_mid,
163
+ "tidings": {k: v.to_dict() for k, v in self.tidings.items()},
164
+ "system_globals": filtered_system_globals,
165
+ }
166
+
167
+ def new_instance(self) -> None:
168
+ self.skills = {}
169
+ self.tidings = {}
170
+ self.globals = {"__messages": []}
171
+ self.last_mid = -1
172
+
173
+ exec("import math", {}, self.globals)
174
+ exec("from typing import *", {}, self.globals)
175
+ exec("from tavily import TavilyClient", {}, self.globals)
176
+ client = self.find_client_in_profile("language_model")
177
+
178
+ def language_model(prompt: str, pre_prompt: str = None) -> str:
179
+ """
180
+ A simple language model calling function that returns the prompt as a response.
181
+
182
+ Use the following guidelines to create effective prompts when calling the function `language_model`:
183
+ - Start with using pre_prompt to assign a role to the language model (e.g. "You are an expert report writer." or "You are a Python code generator.")
184
+ - Begin the prompt by asking the language model to perform a specific task (e.g. "Write a report on the current state of AI research based on the following CONTEXT." or "Generate a Python function that behaviour described in ASSIGNMENT.")
185
+ - Mention the parameters of the prompt in the prompt, i.e. if the prompt operates on a text, mention the name of the parameter (e.g. "CONTEXT" or "ASSIGNMENT") and the type of the parameter (e.g. "text", "list", "dictionary", etc.).
186
+ - Specify the expected output format (e.g. "Write a report in Markdown format. Do not output any other text." or "Generate a Python function that returns a list of numbers and enclose it in ``` and ```. Any text outside the code block will be ignored.")
187
+ - Provide the context or assignment in a clear and concise manner (.e.g "\n\nCONTEXT:\n <context>" or "\n\nASSIGNMENT:\n<assignment>"); use newlines to separate the parameters from the rest of the prompt.
188
+ - If you think the prompt might get long once the parameters are pasted into it, add a list of reminders, e.g. "Reminders:\n- Do not output any other text.\n- Output a report in Markdown format based on the CONTEXT given..."
189
+
190
+ Args:
191
+ prompt (str): The prompt to be sent to the language model.
192
+ pre_prompt (str, optional): An optional pre-prompt (also known as system prompt, can be used to assign an overarching role to the LM) to be sent before the main prompt.
193
+
194
+ Returns:
195
+ str: The response from the language model.
196
+ """
197
+ return client.run(pre_prompt, prompt)
198
+
199
+ if "language_model" not in self.skills:
200
+ self.skills["language_model"] = SkillV4(
201
+ name="language_model",
202
+ source_message="",
203
+ python_name="language_model",
204
+ docstring=language_model.__doc__,
205
+ code=language_model.__code__,
206
+ )
207
+ self.globals["language_model"] = language_model
208
+
209
+ self.execution_trace.write_separator()
210
+ self.execution_trace(
211
+ f"Creating new instance with id {self.instance_id} at {datetime.now()}"
212
+ )
213
+ self.execution_trace(
214
+ f"New instance created; skills, tidings, and globals erased"
215
+ )
216
+ self.execution_trace.write_separator()
217
+
218
+ # HIGH-LEVEL OUTPUT GENERATION CODE
219
+ def generate(
220
+ self, messages: list[dict], frame_config: FrameConfigV4 | None = None
221
+ ) -> str:
222
+ """
223
+ Generates a response to the given messages.
224
+ """
225
+ if frame_config is None:
226
+ frame_config = self.config
227
+
228
+ last_msg = messages[-1]
229
+ ret = self.process_message(
230
+ mid=last_msg["mid"],
231
+ message=last_msg["content"],
232
+ message_type=last_msg["type"],
233
+ )
234
+ return ret
235
+
236
+ # HIGH-LEVEL OUTPUT GENERATION CODE
237
+
238
+ def generate_with_notifications(
239
+ self, messages: list[dict], frame_config: FrameConfigV4 | None = None
240
+ ) -> Generator[dict, None, None]:
241
+ """
242
+ Generates a response to the given messages.
243
+ """
244
+ if frame_config is None:
245
+ frame_config = self.config
246
+
247
+ last_msg = messages[-1]
248
+ for notification in self.process_message(
249
+ mid=last_msg["mid"],
250
+ message=last_msg["content"],
251
+ message_type=last_msg["type"],
252
+ generate_not_return=True,
253
+ ):
254
+ yield notification
255
+
256
+ # MESSAGE HANDLING
257
+ def process_message(
258
+ self,
259
+ mid: int,
260
+ message: str,
261
+ message_type: str | None,
262
+ generate_not_return: bool = False,
263
+ ) -> Any | Generator[dict, None, None]:
264
+ """
265
+ message_type is one of "routine", "routine_skill", "query", "query_skill", "code", "code_skill", "data", "auto" (also representable as None)
266
+ """
267
+ if message.strip() == "":
268
+ raise ValueError("Empty message provided for instruction processing")
269
+ if message_type is None:
270
+ message_type = "auto"
271
+ self.allowed_message_types = [
272
+ "routine",
273
+ "routine_skill",
274
+ "query",
275
+ "query_skill",
276
+ "code",
277
+ "code_skill",
278
+ "data",
279
+ "auto",
280
+ "generating_routine",
281
+ ]
282
+ if message_type not in self.allowed_message_types:
283
+ allowed_message_types_str: str = "', '".join(self.allowed_message_types)
284
+ raise ValueError(
285
+ f"Invalid message type: '{message_type}'; must be one of '{allowed_message_types_str}'"
286
+ )
287
+
288
+ if message_type == "auto":
289
+ message_type = self.decide_message_type(message)
290
+
291
+ skills: list[SkillV4] | None = None
292
+ invocation_code: str | None = None
293
+ variable_descriptions: dict[str, str] | None = None
294
+
295
+ if message_type == "code":
296
+ skills, invocation_code, variable_descriptions = self.process_message_code(
297
+ mid, message, self.tidings
298
+ )
299
+ elif message_type == "code_skill":
300
+ skills = self.process_message_code_skill(
301
+ mid, message
302
+ ) # no tidings, skills are supposed to be pure functions
303
+ elif message_type == "routine":
304
+ skills, invocation_code, variable_descriptions = (
305
+ self.process_message_routine(mid, message, self.tidings)
306
+ )
307
+ elif message_type == "generating_routine":
308
+ if not generate_not_return:
309
+ raise ValueError(
310
+ "Generating routine messages must be processed with generate_not_return=True"
311
+ )
312
+ skills, invocation_code, variable_descriptions = (
313
+ self.process_message_routine(
314
+ mid, message, self.tidings, is_generating_routine=True
315
+ )
316
+ )
317
+ elif message_type == "data":
318
+ invocation_code = self.process_message_data(mid, message)
319
+ skills = []
320
+ else:
321
+ raise NotImplementedError(
322
+ f"Message type '{message_type}' not implemented yet"
323
+ )
324
+
325
+ self.log_compilation_result(mid, skills, invocation_code, variable_descriptions)
326
+
327
+ skill_capture_context = {}
328
+ for skill in skills:
329
+ exec(f"{skill.code}", self.globals, skill_capture_context)
330
+ self.skills[skill.name] = skill
331
+ for context_var, context_value in skill_capture_context.items():
332
+ if not context_var in self.globals:
333
+ self.globals[context_var] = context_value
334
+
335
+ execution_context = {
336
+ tiding.python_name: tiding.content for tiding in self.tidings.values()
337
+ }
338
+ __output = None
339
+ if invocation_code is not None:
340
+ if message_type != "generating_routine":
341
+ exec(invocation_code, self.globals, execution_context)
342
+ if "__output" in execution_context:
343
+ __output = execution_context["__output"]
344
+ if "__vars" in execution_context:
345
+ __vars = execution_context["__vars"]
346
+ for var_name, var_value in __vars.items():
347
+ self.tidings[var_name] = Tiding(
348
+ natural_name=var_name,
349
+ python_name=var_name,
350
+ content=var_value,
351
+ description=(
352
+ variable_descriptions[var_name]
353
+ if variable_descriptions is not None
354
+ and var_name in variable_descriptions
355
+ else ""
356
+ ),
357
+ )
358
+ else:
359
+ exec(invocation_code, self.globals, execution_context)
360
+ __generator = execution_context["__generator"]
361
+ __final: dict[str, Any] = {}
362
+ for notification in __generator:
363
+ if notification["type"] == "final":
364
+ __final = notification
365
+ else:
366
+ yield notification
367
+ if "modified_vars" in __final:
368
+ for var_name, var_value in __final["modified_vars"].items():
369
+ self.tidings[var_name] = Tiding(
370
+ natural_name=var_name,
371
+ python_name=var_name,
372
+ content=var_value,
373
+ description=(
374
+ variable_descriptions[var_name]
375
+ if variable_descriptions is not None
376
+ and var_name in variable_descriptions
377
+ else ""
378
+ ),
379
+ )
380
+
381
+ self.last_mid = mid
382
+
383
+ if not generate_not_return:
384
+ return __output
385
+
386
+ def decide_message_type(self, message: str) -> str:
387
+ """
388
+ Decides the message type based on the content of the message.
389
+
390
+ Returns one of "routine", "routine_skill", "query", "query_skill", "code", "code_skill", "data"
391
+ """
392
+ message_type_errand: Errand = self.find_errand_in_profile("message_type")()
393
+ message_type_client = self.find_client_in_profile("message_type")
394
+
395
+ message_type: str | None = message_type_errand.run(
396
+ runner=message_type_client,
397
+ args={"message": message},
398
+ )
399
+
400
+ if message_type is None or message_type not in self.allowed_message_types:
401
+ raise ValueError(
402
+ f"Message type could not be determined for message: '{message}'"
403
+ )
404
+
405
+ return message_type
406
+
407
+ # MESSAGE PROCESSING
408
+ def process_message_code(
409
+ self, mid: int, message: str, tidings: dict[str, Tiding]
410
+ ) -> tuple[list[SkillV4], str | None, dict[str, str] | None]:
411
+ """
412
+ Processes a code message and returns the corresponding skill(s), invocation code, and variable descriptions.
413
+ """
414
+ code_processing_errand: Errand = self.find_errand_in_profile(
415
+ "message_code_processing"
416
+ )()
417
+ code_processing_client = self.find_client_in_profile("message_code_processing")
418
+
419
+ # serialize tidings for prompt injection
420
+ serialized_tidings = "\n".join(
421
+ [f"{k}: {v.content} # {v.description}" for k, v in tidings.items()]
422
+ )
423
+ code: str = code_processing_errand.run(
424
+ runner=code_processing_client,
425
+ args={"message": message, "tidings": serialized_tidings},
426
+ )
427
+ code = self.sanitize_code(code)
428
+
429
+ # replace the first occurence of code with f"message_{mid}_code"
430
+ code = code.replace("code", f"message_{mid}_code", 1)
431
+ docstring_addendum = f"\n\nThis function was generated to fulfill the intent of the user message with message id {mid}."
432
+
433
+ func_defs = self.extract_function_definitions(code)
434
+ skills = []
435
+ variable_descriptions: dict[str, str] | None = None
436
+ if func_defs:
437
+ func = func_defs[0]
438
+ func_code = func["code"].replace(
439
+ func["docstring"], func["docstring"] + docstring_addendum, 1
440
+ )
441
+ skills.append(
442
+ SkillV4(
443
+ name=func["python_name"],
444
+ source_message=message,
445
+ python_name=func["python_name"],
446
+ docstring=func["docstring"] + docstring_addendum,
447
+ code=func_code,
448
+ )
449
+ )
450
+
451
+ # TODO: perhaps, in the future, let the user know that other functions are present but ignored
452
+
453
+ message_code_call_errand: Errand = self.find_errand_in_profile(
454
+ "message_code_call"
455
+ )()
456
+ message_code_call_client = self.find_client_in_profile("message_code_call")
457
+ invocation_code = message_code_call_errand.run(
458
+ runner=message_code_call_client,
459
+ args={
460
+ "message": message,
461
+ "code": skills[0].code,
462
+ "tidings": serialized_tidings,
463
+ },
464
+ )
465
+
466
+ # Use MessageCodeVariablesErrand to get variable descriptions
467
+ message_code_variables_errand: Errand = self.find_errand_in_profile(
468
+ "message_code_variables"
469
+ )()
470
+ message_code_variables_client = self.find_client_in_profile(
471
+ "message_code_variables"
472
+ )
473
+ variable_descriptions_raw = message_code_variables_errand.run(
474
+ runner=message_code_variables_client,
475
+ args={
476
+ "message": message,
477
+ "code": skills[0].code,
478
+ "tidings": serialized_tidings,
479
+ },
480
+ )
481
+ # Parse the output into a dict
482
+ variable_descriptions = {}
483
+ for line in variable_descriptions_raw.strip().splitlines():
484
+ if "#" in line:
485
+ var, desc = line.split("#", 1)
486
+ variable_descriptions[var.strip()] = desc.strip()
487
+
488
+ return skills, invocation_code, variable_descriptions
489
+ else:
490
+ return [], None, None
491
+
492
+ def process_message_code_skill(self, mid: int, message: str) -> list[SkillV4]:
493
+ """
494
+ Processes a code_skill message and returns the corresponding skill(s).
495
+ """
496
+ code_skill_processing_errand: Errand = self.find_errand_in_profile(
497
+ "message_code_skill_processing"
498
+ )()
499
+ code_skill_processing_client = self.find_client_in_profile(
500
+ "message_code_skill_processing"
501
+ )
502
+ code: str = code_skill_processing_errand.run(
503
+ runner=code_skill_processing_client,
504
+ args={"message": message},
505
+ )
506
+ code = self.sanitize_code(code)
507
+
508
+ docstring_addendum = f"\n\nThis function was generated to fulfill the intent of the user message with message id {mid}."
509
+
510
+ func_defs = self.extract_function_definitions(code)
511
+ skills = []
512
+ if func_defs:
513
+ for func in func_defs:
514
+ func_code = func["code"].replace(
515
+ func["docstring"], func["docstring"] + docstring_addendum, 1
516
+ )
517
+ skills.append(
518
+ SkillV4(
519
+ name=func["python_name"],
520
+ source_message=message,
521
+ python_name=func["python_name"],
522
+ docstring=func["docstring"] + docstring_addendum,
523
+ code=func_code,
524
+ )
525
+ )
526
+ return skills
527
+
528
+ def process_message_routine(
529
+ self,
530
+ mid: int,
531
+ message: str,
532
+ tidings: dict[str, Tiding],
533
+ is_generating_routine: bool = False,
534
+ ) -> tuple[list[SkillV4], str | None, dict[str, str] | None]:
535
+ """
536
+ Processes a routine message and returns the corresponding skill(s), invocation code, and variable descriptions.
537
+ """
538
+ routine_processing_errand: Errand = (
539
+ self.find_errand_in_profile("message_routine_processing")()
540
+ if not is_generating_routine
541
+ else self.find_errand_in_profile("message_generating_routine_processing")()
542
+ )
543
+ routine_processing_client = (
544
+ self.find_client_in_profile("message_routine_processing")
545
+ if not is_generating_routine
546
+ else self.find_client_in_profile("message_generating_routine_processing")
547
+ )
548
+
549
+ # Prepare skills and tidings for prompt injection
550
+ serialized_skills = "\n\n".join(
551
+ [f"function {k}\n---\n{v.docstring}" for k, v in self.skills.items()]
552
+ )
553
+ serialized_tidings = "\n".join(
554
+ [f"{k} = {v.content} # {v.description}" for k, v in tidings.items()]
555
+ )
556
+ code: str = routine_processing_errand.run(
557
+ runner=routine_processing_client,
558
+ args={
559
+ "message": message,
560
+ "skills": serialized_skills,
561
+ "tidings": serialized_tidings,
562
+ },
563
+ )
564
+ code = self.sanitize_code(code)
565
+
566
+ # replace the first occurrence of code with f"message_{mid}_routine_code"
567
+ code = code.replace("code", f"message_{mid}_routine_code", 1)
568
+ docstring_addendum = f"\n\nThis function was generated to fulfill the intent of the user message with message id {mid}."
569
+
570
+ func_defs = self.extract_function_definitions(code)
571
+ skills = []
572
+ variable_descriptions: dict[str, str] | None = None
573
+ if func_defs:
574
+ func = func_defs[0]
575
+ func_code = func["code"].replace(
576
+ func["docstring"], func["docstring"] + docstring_addendum, 1
577
+ )
578
+ skills.append(
579
+ SkillV4(
580
+ name=func["python_name"],
581
+ source_message=message,
582
+ python_name=func["python_name"],
583
+ docstring=func["docstring"] + docstring_addendum,
584
+ code=func_code,
585
+ )
586
+ )
587
+
588
+ # Generate invocation code using MessageRoutineCallErrand
589
+ message_routine_call_errand: Errand = (
590
+ self.find_errand_in_profile("message_routine_call")()
591
+ if not is_generating_routine
592
+ else self.find_errand_in_profile("message_generating_routine_call")()
593
+ )
594
+ message_routine_call_client = (
595
+ self.find_client_in_profile("message_routine_call")
596
+ if not is_generating_routine
597
+ else self.find_client_in_profile("message_generating_routine_call")
598
+ )
599
+ invocation_code = message_routine_call_errand.run(
600
+ runner=message_routine_call_client,
601
+ args={
602
+ "message": message,
603
+ "code": skills[0].code,
604
+ "tidings": serialized_tidings,
605
+ },
606
+ )
607
+
608
+ # Generate variable descriptions using MessageRoutineVariablesErrand
609
+ message_routine_variables_errand: Errand = self.find_errand_in_profile(
610
+ "message_routine_variables"
611
+ )()
612
+ message_routine_variables_client = self.find_client_in_profile(
613
+ "message_routine_variables"
614
+ )
615
+ variable_descriptions_raw = message_routine_variables_errand.run(
616
+ runner=message_routine_variables_client,
617
+ args={
618
+ "message": message,
619
+ "code": skills[0].code,
620
+ "tidings": serialized_tidings,
621
+ },
622
+ )
623
+ # Parse the output into a dict
624
+ variable_descriptions = {}
625
+ for line in variable_descriptions_raw.strip().splitlines():
626
+ if "#" in line:
627
+ var, desc = line.split("#", 1)
628
+ variable_descriptions[var.strip()] = desc.strip()
629
+
630
+ return skills, invocation_code, variable_descriptions
631
+ else:
632
+ return [], None, None
633
+
634
+ def process_message_data(self, mid: int, message: str) -> str:
635
+ """
636
+ Processes a data message and returns the corresponding Python code that, when executed, loads variables into memory.
637
+ """
638
+ data_processing_errand: Errand = self.find_errand_in_profile(
639
+ "message_data_processing"
640
+ )()
641
+ data_processing_client = self.find_client_in_profile("message_data_processing")
642
+
643
+ code: str = data_processing_errand.run(
644
+ runner=data_processing_client,
645
+ args={"message": message},
646
+ )
647
+ code = self.sanitize_code(code)
648
+ return code
649
+
650
+ # MISC
651
+ def make_trace_from_arg(
652
+ self, path: str, trace_arg: bool | str | Trace | None = None
653
+ ) -> Trace:
654
+ if trace_arg is None:
655
+ return Trace(None)
656
+ if isinstance(trace_arg, Trace):
657
+ return trace_arg
658
+ if isinstance(trace_arg, bool):
659
+ if trace_arg is True:
660
+ return Trace(path)
661
+ else:
662
+ return Trace(None)
663
+ if isinstance(trace_arg, str):
664
+ if trace_arg == "stdout":
665
+ return Trace(None, copy_into_stdout=True)
666
+ if trace_arg == "file_and_stdout":
667
+ return Trace(path, copy_into_stdout=True)
668
+ else:
669
+ raise ValueError(f"Invalid value for trace: {trace_arg}")
670
+
671
+ def log_compilation_result(
672
+ self,
673
+ mid: int,
674
+ skills: list[SkillV4] | None,
675
+ invocation_code: str | None,
676
+ variable_descriptions: dict[str, str] | None,
677
+ ) -> None:
678
+ self.compilation_trace.write_separator()
679
+ self.compilation_trace(f"Compiled message {mid} at {datetime.now()}")
680
+ if invocation_code is not None:
681
+ self.compilation_trace(f"Invocation code: {invocation_code}")
682
+ if variable_descriptions is not None:
683
+ self.compilation_trace(f"Variable descriptions: {variable_descriptions}")
684
+
685
+ if skills is not None:
686
+ for skill in skills:
687
+ self.compilation_trace("*" * 20)
688
+ self.compilation_trace(f"Skill name: {skill.name}")
689
+ self.compilation_trace(f"Python name: {skill.python_name}")
690
+ self.compilation_trace(f"Docstring:\n{skill.docstring}")
691
+ self.compilation_trace("-" * 20)
692
+ self.compilation_trace(f"Code:\n{skill.code}")
693
+ self.compilation_trace("-" * 20)
694
+ self.compilation_trace(f"Source message:\n{skill.source_message}")
695
+
696
+ self.compilation_trace.write_separator()
697
+
698
+ def find_errand_in_profile(self, designation: str) -> type[Errand]:
699
+ if designation in self.errand_profile:
700
+ return self.errand_profile[designation]
701
+ raise ValueError(
702
+ f"Errand choice with designation '{designation}' not found in the errand profile"
703
+ )
704
+
705
+ def find_client_in_profile(self, designation: str) -> Client:
706
+ if designation in self.client_profile:
707
+ return self.client_profile[designation]
708
+ raise ValueError(
709
+ f"Client choice with designation '{designation}' not found in the client profile"
710
+ )
711
+
712
+ def extract_function_definitions(self, code: str) -> list[dict[str, Any]]:
713
+ """
714
+ Parses the given Python code and returns a list of dictionaries, each containing:
715
+ - python_name: the function name
716
+ - args: a list of argument names
717
+ - docstring: the function docstring (or None)
718
+ - code: the full source code of the function, including the signature
719
+ Only top-level (module-level) functions are extracted; nested functions are ignored.
720
+ """
721
+ results = []
722
+ try:
723
+ tree = ast.parse(code)
724
+ except Exception as e:
725
+ return []
726
+
727
+ # Helper to get source code for a node
728
+ def get_source_segment(node):
729
+ try:
730
+ return ast.get_source_segment(code, node)
731
+ except Exception:
732
+ lines = code.splitlines()
733
+ if hasattr(node, "lineno") and hasattr(node, "end_lineno"):
734
+ return "\n".join(lines[node.lineno - 1 : node.end_lineno])
735
+ return None
736
+
737
+ # Only consider top-level functions (direct children of Module)
738
+ for node in tree.body:
739
+ if isinstance(node, ast.FunctionDef):
740
+ func_name = node.name
741
+ args = [arg.arg for arg in node.args.args]
742
+ docstring = ast.get_docstring(node)
743
+ func_code = get_source_segment(node)
744
+ results.append(
745
+ {
746
+ "python_name": func_name,
747
+ "args": args,
748
+ "docstring": docstring,
749
+ "code": func_code,
750
+ }
751
+ )
752
+ return results
753
+
754
+ def sanitize_code(self, code: str) -> str:
755
+ """
756
+ Sanitizes the code by removing ```python and ``` tags. Note that the code might also start with ``` and end with ```.
757
+ """
758
+ # Remove the first and last lines if they are empty
759
+ lines = code.splitlines()
760
+ if len(lines) > 0 and lines[0].strip() == "":
761
+ lines = lines[1:]
762
+ if len(lines) > 0 and lines[-1].strip() == "":
763
+ lines = lines[:-1]
764
+
765
+ # Remove the first and last lines if they are ```python or ```
766
+ if len(lines) > 0 and (
767
+ lines[0].strip() == "```python" or lines[0].strip() == "```"
768
+ ):
769
+ lines = lines[1:]
770
+ if len(lines) > 0 and (
771
+ lines[-1].strip() == "```" or lines[-1].strip() == "```python"
772
+ ):
773
+ lines = lines[:-1]
774
+
775
+ return "\n".join(lines)
backend/frame/prompts/udr_minimal/0.code_skill.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ def send_message(type, description, **kwargs):
16
+ """
17
+ Sends a message by appending it to the global `__messages` list.
18
+
19
+ This function creates a message dictionary with a specified type and description,
20
+ along with any additional keyword arguments provided. The constructed message is
21
+ then added to the global `__messages` list for further processing or logging.
22
+
23
+ Args:
24
+ type (str): The type of the message (e.g., "error", "info", "warning").
25
+ description (str): A brief description of the message content.
26
+ **kwargs: Additional key-value pairs to include in the message dictionary.
27
+
28
+ Raises:
29
+ NameError: If the global variable `__messages` is not defined.
30
+
31
+ Example:
32
+ send_message(
33
+ type="info",
34
+ description="Process completed successfully",
35
+ report="Reporting information" # optional, added for this particular example
36
+ )
37
+ """
38
+ global __messages
39
+ message = {"type": type, "description": description, **kwargs}
40
+ __messages.append(message)
backend/frame/prompts/udr_minimal/1.code_skill.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ def perform_search(search_phrase: str):
16
+ """
17
+ Performs a search using the Tavily API and returns a list of search result objects.
18
+
19
+ Args:
20
+ search_phrase (str): The phrase to search for using the Tavily API.
21
+
22
+ Returns:
23
+ List[Dict[str, Any]]: A list of search result dictionaries obtained from the Tavily API. Each dictionary has 'content' field with text retrieved from the result, and a 'url' field that keeps the url of the search result.
24
+
25
+ Raises:
26
+ TavilyClientError: If there is an issue with the Tavily API client or the search request.
27
+ KeyError: If the expected 'results' key is missing in the search response.
28
+
29
+ """
30
+ from tavily import TavilyClient
31
+
32
+ client = TavilyClient(api_key="tvly-dev-XXXX")
33
+ search_response = client.search(search_phrase, include_raw_content=True)
34
+ return search_response["results"]
backend/frame/prompts/udr_minimal/2.auto.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ The following is the prompt data to be used in later procedures.
2
+
3
+ PROMPT:
4
+ Produce a comprehensive report on the major Christian events that took place this year between the 20th and 22nd of April 2025.
backend/frame/prompts/udr_minimal/3.auto.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1. Send a message of type "prompt_received" with description saying what PROMPT has been received, e.g. "Received research request: {PROMPT}"
2
+ 2. Send a message of type "prompt_analysis_started", with description indicating that we are now analyzing the research request.
3
+ 3. Take the PROMPT and ask a language model to produce 3 search phrases that could help with retrieving results from search engine for the purpose of compiling a report the user asks for in the PROMPT. The search phrases should be simple and objective, e.g. "important events 1972" or "energy consumption composition in India today". Use a long prompt for the model that describes in detail what is supposed to be performed and the expected output format. Instruct the model to return the search phrases on one line each. Tell the model not to output any other text -- just the newline-separated phrases. Then, parse the output of the language model line by line and save the resulting search phrases as "phrases" for further research, skipping over empty lines.
4
+ 4. Send a message of type "prompt_analysis_completed", with a description saying as much.
5
+ 5. For each phrase in phrases output by step 3., perform the following:
6
+ - Send a message of type "search_started", with the description indicating what search phrase we are using for the search, e.g. "Searching for phrase '{phrase}'"
7
+ - Perform search with the phrase. Once the search returns some results, append their contents to CONTEXT one by one, separating them by double newlines from what is already present in the CONTEXT.
8
+ 6. Send a message with type "report_building", with the description indicating that the report is being built.
9
+ 7. Take CONTEXT. Call the language model, instructing it to take CONTEXT (to be appended into the LM call) and produce a deep research report on the topic requested in PROMPT. The resulting report should go into detail wherever possible, rely only on the information available in CONTEXT, address the instruction given in the PROMPT, and be formatted in Markdown. This is to be communicated in the prompt. Do not shy away from using long, detailed and descriptive prompts! Tell the model not to output any other text, just the report. The result produced by the language model is to be called REPORT.
10
+ 8. Send a message with type "report_done", indicating that the report has been completed. Add "report" as a field containing the REPORT to be an additional payload to the message.
11
+ 9. Output the REPORT.
backend/frame/prompts/udr_minimal_generating/0.code_skill.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ def perform_search(search_phrase: str):
16
+ """
17
+ Performs a search using the Tavily API and returns a list of search result objects.
18
+
19
+ Args:
20
+ search_phrase (str): The phrase to search for using the Tavily API.
21
+
22
+ Returns:
23
+ List[Dict[str, Any]]: A list of search result dictionaries obtained from the Tavily API. Each dictionary has 'content' field with text retrieved from the result, and a 'url' field that keeps the url of the search result.
24
+
25
+ Raises:
26
+ TavilyClientError: If there is an issue with the Tavily API client or the search request.
27
+ KeyError: If the expected 'results' key is missing in the search response.
28
+
29
+ """
30
+ from tavily import TavilyClient
31
+
32
+ client = TavilyClient(api_key="tvly-dev-XXXX")
33
+ search_response = client.search(search_phrase, include_raw_content=True)
34
+ return search_response["results"]
backend/frame/prompts/udr_minimal_generating/1.auto.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ The following is the prompt data to be used in later procedures.
2
+
3
+ PROMPT:
4
+ Produce a comprehensive report on the major Christian events that took place this year between the 20th and 22nd of April 2025.
backend/frame/prompts/udr_minimal_generating/2.routine_generating.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1. Send a notification of type "prompt_received" with description saying what PROMPT has been received, e.g. "Received research request: {PROMPT}"
2
+ 2. Send a notification of type "prompt_analysis_started", with description indicating that we are now analyzing the research request.
3
+ 3. Take the PROMPT and ask a language model to produce 3 search phrases that could help with retrieving results from search engine for the purpose of compiling a report the user asks for in the PROMPT. The search phrases should be simple and objective, e.g. "important events 1972" or "energy consumption composition in India today". Use a long prompt for the model that describes in detail what is supposed to be performed and the expected output format. Instruct the model to return the search phrases on one line each. Tell the model not to output any other text -- just the newline-separated phrases. Then, parse the output of the language model line by line and save the resulting search phrases as "phrases" for further research, skipping over empty lines.
4
+ 4. Send a notification of type "prompt_analysis_completed", with a description saying as much.
5
+ 4.1 Send a notification of type "task_analysis_completed", informing the user that the search plan has been completed and informing them how many search phrases will be invoked, e.g. "Search planning completed. Will be searching through {len(topics)}+ terms."
6
+ 5. For each phrase in phrases output by step 3., perform the following:
7
+ - Send a notification of type "search_started", with the description indicating what search phrase we are using for the search, e.g. "Searching for phrase '{phrase}'"
8
+ - Perform search with the phrase.
9
+ - Once the search returns some results, append their contents to CONTEXT one by one, separating them by double newlines from what is already present in the CONTEXT.
10
+ - Send a notification of type "search_result_processing_completed", indicating in its description that the search results for term {term} have been processed.
11
+ 6. Send a notification to the user with type "research_completed", indicating that the "Research phase is now completed.".
12
+ 7. Send a notification with type "report_building", with the description indicating that the report is being built.
13
+ 8. Take CONTEXT. Call the language model, instructing it to take CONTEXT (to be appended into the LM call) and produce a deep research report on the topic requested in PROMPT. The resulting report should go into detail wherever possible, rely only on the information available in CONTEXT, address the instruction given in the PROMPT, and be formatted in Markdown. This is to be communicated in the prompt. Do not shy away from using long, detailed and descriptive prompts! Tell the model not to output any other text, just the report. The result produced by the language model is to be called REPORT.
14
+ 9. Send a notification with type "report_done", indicating that the report has been completed. Add "report" as a field containing the REPORT to be an additional payload to the notification.
15
+ 10. Output the REPORT.
backend/frame/routines.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import importlib.util
16
+ import json
17
+ import sys
18
+ import types
19
+
20
+ import docstring_parser
21
+
22
+
23
+ def import_from_path(module_name, file_path):
24
+ """Import a module given its name and file path."""
25
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
26
+ module = importlib.util.module_from_spec(spec)
27
+ sys.modules[module_name] = module
28
+ spec.loader.exec_module(module)
29
+ return module
30
+
31
+
32
+ class Routine:
33
+ def __init__(
34
+ self,
35
+ name: str,
36
+ description: str,
37
+ parameters: dict[str, str],
38
+ fn: callable,
39
+ returns: dict[str, str] | None = None,
40
+ module: str | None = None,
41
+ source_file: str | None = None,
42
+ ) -> None:
43
+ self.name: str = name
44
+ self.description: str = description
45
+ self.parameters: dict[str, dict[str, str]] = parameters
46
+ self.returns: dict[str, str] | None = returns
47
+ self.fn = fn
48
+
49
+ self.module: str | None = module
50
+ self.source_file: str | None = source_file
51
+
52
+ def __str__(self) -> str:
53
+ ret = "Name: " + self.name + "\n"
54
+ ret += "Description: " + self.description + "\n"
55
+ ret += "Parameters:\n"
56
+ for key, props in self.parameters.items():
57
+ ret += f" {key}:\n"
58
+ for prop, value in props.items():
59
+ ret += f" {prop}: {value}\n"
60
+ return ret
61
+
62
+ def to_dict(self) -> dict:
63
+ return {
64
+ "name": self.name,
65
+ "description": self.description,
66
+ "parameters": {"type": "object", "properties": self.parameters},
67
+ }
68
+
69
+ def to_json(self) -> str:
70
+ return json.dumps(self.to_dict(), indent=4)
71
+
72
+
73
+ class RoutineRegistry:
74
+ def __init__(self, routines: list[Routine] | None = None) -> None:
75
+ self.routines: list[Routine] = routines if routines is not None else []
76
+
77
+ def add_routine(self, routine: Routine) -> None:
78
+ self.routines.append(routine)
79
+
80
+ def remove_routine(self, routine: Routine) -> None:
81
+ self.routines.remove(routine)
82
+
83
+ def get_routine_of_name(self, name: str) -> Routine:
84
+ return next(
85
+ (
86
+ routine
87
+ for routine in self.routines
88
+ if routine.name.lower() == name.lower()
89
+ ),
90
+ None,
91
+ )
92
+
93
+ def to_list(self) -> list[dict]:
94
+ return [routine.to_dict() for routine in self.routines]
95
+
96
+ def to_json(self) -> str:
97
+ return json.dumps(self.to_list(), indent=4)
98
+
99
+ def load_from_file(self, module_name: str, file_path: str) -> None:
100
+ module = import_from_path(module_name, file_path)
101
+ for name, obj in module.__dict__.items():
102
+ if type(obj) == types.FunctionType:
103
+ doc = docstring_parser.parse(obj.__doc__)
104
+ parameters = {
105
+ param.arg_name: {
106
+ "type": param.type_name,
107
+ "description": param.description,
108
+ }
109
+ for param in doc.params
110
+ }
111
+ returns = {
112
+ "type": doc.returns.type_name,
113
+ "description": doc.returns.description,
114
+ }
115
+ self.add_routine(
116
+ Routine(
117
+ name,
118
+ doc.short_description
119
+ + "\n"
120
+ + (
121
+ doc.long_description
122
+ if doc.long_description is not None
123
+ else ""
124
+ ),
125
+ parameters,
126
+ obj,
127
+ returns,
128
+ module_name,
129
+ file_path,
130
+ )
131
+ )
backend/frame/tidings.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from typing import Any
16
+
17
+
18
+ class Tiding:
19
+ def __init__(
20
+ self, natural_name: str, python_name: str, description: str, content: Any
21
+ ) -> None:
22
+ self.natural_name = natural_name
23
+ self.python_name = python_name
24
+ self.description = description
25
+ self.content = content
26
+
27
+ def __str__(self) -> str:
28
+ return f"Natural name: {self.natural_name}\nPython name: {self.python_name}\nDescription: {self.description}\nContent: {self.content}"
29
+
30
+ def to_dict(self) -> dict:
31
+ return {
32
+ "natural_name": self.natural_name,
33
+ "python_name": self.python_name,
34
+ "description": self.description,
35
+ "content": self.content,
36
+ "type": type(self.content).__name__,
37
+ }
38
+
39
+ def from_dict(self, tiding: dict) -> None:
40
+ self.natural_name = tiding["natural_name"]
41
+ self.python_name = tiding["python_name"]
42
+ self.description = tiding["description"]
43
+ self.content = tiding["content"]
backend/frame/trace.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import os
16
+ import sys
17
+ from typing import Callable
18
+
19
+
20
+ class Trace:
21
+ def __init__(
22
+ self,
23
+ path: str | None = None,
24
+ copy_into_stdout: bool = False,
25
+ hook: Callable = None,
26
+ ) -> None:
27
+ self.path = path
28
+ self.entries = []
29
+ self.copy_into_stdout = copy_into_stdout
30
+ self.hook = hook
31
+
32
+ # set self.file to a new file if path is not None or to stdout if it is None
33
+ self.file = open(path, "w") if path is not None else open(os.devnull, "w")
34
+
35
+ def write(self, entry: str) -> None:
36
+ self.entries.append(entry)
37
+ print(entry, file=self.file, flush=True)
38
+ if self.copy_into_stdout:
39
+ print(entry, file=sys.stdout, flush=True)
40
+ if self.hook is not None:
41
+ self.hook(entry)
42
+
43
+ def close(self) -> None:
44
+ self.file.close()
45
+
46
+ def __call__(self, *args) -> None:
47
+ for arg in args:
48
+ self.write(str(arg))
49
+
50
+ def write_separator(self) -> None:
51
+ self.write("#" * 80)
backend/insert_license.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import os
16
+ from typing import List
17
+
18
+ def add_header_to_python_files(directory_path, header_content, extensions: List[str] = [".py"]):
19
+ """
20
+ Inserts a specified header at the beginning of every .py file
21
+ in the given directory and its subdirectories.
22
+
23
+ Args:
24
+ directory_path (str): The path to the directory to process.
25
+ header_content (str): The content of the header to insert.
26
+ Ensure it includes newlines for proper formatting.
27
+ """
28
+ for root, _, files in os.walk(directory_path):
29
+ for file_name in files:
30
+ if file_name == '.venv':
31
+ continue
32
+ for extension in extensions:
33
+ if file_name.endswith(extension):
34
+ file_path = os.path.join(root, file_name)
35
+ try:
36
+ with open(file_path, 'r+', encoding='utf-8') as f:
37
+ original_content = f.read()
38
+ if original_content.startswith(header_content):
39
+ print(f"Header already exists in: {file_path}")
40
+ continue
41
+ f.seek(0) # Move cursor to the beginning of the file
42
+ f.write(header_content + original_content)
43
+ print(f"Header added to: {file_path}")
44
+ except IOError as e:
45
+ print(f"Error processing {file_path}: {e}")
46
+
47
+ if __name__ == "__main__":
48
+ target_directory = "../frontend" # Replace with your target directory
49
+
50
+ # Define your header content here. Use triple quotes for multi-line strings.
51
+ # Ensure a newline character at the end of the header for separation.
52
+ custom_header = """/*
53
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
54
+ * SPDX-License-Identifier: Apache-2.0
55
+ *
56
+ * Licensed under the Apache License, Version 2.0 (the "License");
57
+ * you may not use this file except in compliance with the License.
58
+ * You may obtain a copy of the License at
59
+ *
60
+ * http://www.apache.org/licenses/LICENSE-2.0
61
+ *
62
+ * Unless required by applicable law or agreed to in writing, software
63
+ * distributed under the License is distributed on an "AS IS" BASIS,
64
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
65
+ * See the License for the specific language governing permissions and
66
+ * limitations under the License.
67
+ */
68
+ """
69
+
70
+ add_header_to_python_files(target_directory, custom_header, extensions=[".js", ".ts", ".tsx", ".jsx", ".css"])
backend/items.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ Data persistence utilities for the Universal Deep Research Backend (UDR-B).
17
+
18
+ This module provides functions for storing and loading research artifacts,
19
+ events, and other data in JSONL format for easy processing and analysis.
20
+ """
21
+
22
+ import json
23
+ import os
24
+ from typing import Any, Dict, List
25
+
26
+
27
+ def store_items(items: List[Dict[str, Any]], filepath: str) -> None:
28
+ """
29
+ Stores a list of items line by line in a file, with proper string escaping.
30
+ Each item is stored as a JSON string on a separate line.
31
+
32
+ Args:
33
+ items: List of dictionaries to store
34
+ filepath: Path to the file where items will be stored
35
+
36
+ Example:
37
+ store_items([
38
+ {"type": "event1", "message": "Hello \"world\""},
39
+ {"type": "event2", "message": "Line\nbreak"}
40
+ ], "events.jsonl")
41
+ """
42
+ # Create directory if it doesn't exist
43
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
44
+
45
+ with open(filepath, "w", encoding="utf-8") as f:
46
+ for item in items:
47
+ # Convert dictionary to JSON string and write with newline
48
+ json_str = json.dumps(item, ensure_ascii=False)
49
+ f.write(json_str + "\n")
50
+
51
+
52
+ def load_items(filepath: str) -> List[Dict[str, Any]]:
53
+ """
54
+ Loads items from a file where each line is a JSON string.
55
+
56
+ Args:
57
+ filepath: Path to the file containing the items
58
+
59
+ Returns:
60
+ List of items loaded from the file
61
+
62
+ Example:
63
+ items = load_items("events.jsonl")
64
+ for item in items:
65
+ print(item["type"], item["message"])
66
+ """
67
+ if not os.path.exists(filepath):
68
+ return []
69
+
70
+ items = []
71
+ with open(filepath, "r", encoding="utf-8") as f:
72
+ for line in f:
73
+ if line.strip(): # Skip empty lines
74
+ item = json.loads(line)
75
+ items.append(item)
76
+ return items
77
+
78
+
79
+ def register_item(filepath: str, item: Dict[str, Any]) -> None:
80
+ """
81
+ Appends a single item to the specified file.
82
+ Creates the file and its directory if they don't exist.
83
+
84
+ Args:
85
+ filepath: Path to the file where the item will be appended
86
+ item: Dictionary to append to the file
87
+
88
+ Example:
89
+ register_item(
90
+ "events.jsonl",
91
+ {"type": "event3", "message": "New event"}
92
+ )
93
+ """
94
+ # Create directory if it doesn't exist
95
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
96
+
97
+ # Append the item to the file
98
+ with open(filepath, "a", encoding="utf-8") as f:
99
+ json_str = json.dumps(item, ensure_ascii=False)
100
+ f.write(json_str + "\n")
101
+
102
+
103
+ def find_item_by_type(filepath: str, item_type: str) -> Dict[str, Any]:
104
+ """
105
+ Finds an item by its type in the specified file.
106
+
107
+ Args:
108
+ filepath: Path to the file containing the items
109
+ item_type: Type of the item to find
110
+
111
+ Returns:
112
+ Item found in the file
113
+ """
114
+ items = load_items(filepath)
115
+ return next((item for item in items if item["type"] == item_type), None)
backend/launch_server.sh ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Universal Deep Research Backend (UDR-B) - Server Launch Script
4
+ # This script launches the FastAPI server with proper configuration
5
+
6
+ # Load environment variables if .env file exists
7
+ if [ -f ".env" ]; then
8
+ echo "Loading environment variables from .env file..."
9
+ export $(cat .env | grep -v '^#' | xargs)
10
+ fi
11
+
12
+ # Set default values if not provided
13
+ HOST=${HOST:-0.0.0.0}
14
+ PORT=${PORT:-8000}
15
+ LOG_LEVEL=${LOG_LEVEL:-info}
16
+
17
+ echo "Starting Universal Deep Research Backend (UDR-B)..."
18
+ echo "Host: $HOST"
19
+ echo "Port: $PORT"
20
+ echo "Log Level: $LOG_LEVEL"
21
+
22
+ # Launch the server
23
+ uvicorn main:app \
24
+ --reload \
25
+ --env-file .env \
26
+ --access-log \
27
+ --log-level=$LOG_LEVEL \
28
+ --host $HOST \
29
+ --port $PORT \
30
+ > uvicorn_main.txt 2>&1 &
31
+
32
+ # Wait a moment for the process to start
33
+ sleep 2
34
+
35
+ # Find the uvicorn process ID using ps and grep
36
+ PID=$(ps aux | grep "uvicorn main:app" | grep -v grep | awk '{print $2}' | head -1)
37
+
38
+ if [ -n "$PID" ]; then
39
+ echo "Server started with PID: $PID"
40
+ echo "To stop the server, run: kill $PID"
41
+ else
42
+ echo "Warning: Could not find uvicorn process ID"
43
+ fi
44
+
45
+ # Disown the process so it continues running after the script exits
46
+ disown $!
47
+
48
+ echo "Server started in background. Check uvicorn_main.txt for logs."
49
+ echo "API available at: http://$HOST:$PORT"
backend/main.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ Universal Deep Research Backend (UDR-B) - FastAPI Application
17
+
18
+ This module provides the main FastAPI application for the Universal Deep Research Backend,
19
+ offering intelligent research and reporting capabilities through streaming APIs.
20
+ """
21
+
22
+ import asyncio
23
+ import json
24
+ import os
25
+ import random
26
+ from datetime import datetime
27
+ from typing import Any, AsyncGenerator, Dict, Optional
28
+
29
+ import uvicorn
30
+ from fastapi import FastAPI, HTTPException
31
+ from fastapi.middleware.cors import CORSMiddleware
32
+ from fastapi.responses import StreamingResponse
33
+ from pydantic import BaseModel
34
+ from uvicorn.config import LOGGING_CONFIG
35
+
36
+ import items
37
+
38
+ # Import configuration
39
+ from config import get_config
40
+ from frame.clients import Client, HuggingFaceClient, OpenAIClient
41
+ from frame.harness4 import FrameConfigV4, FrameV4
42
+ from frame.trace import Trace
43
+ from scan_research import do_reporting as real_reporting
44
+ from scan_research import do_research as real_research
45
+ from scan_research import generate_session_key
46
+ from scan_research_dry import do_reporting as dry_reporting
47
+ from scan_research_dry import do_research as dry_research
48
+
49
+ # Get configuration
50
+ config = get_config()
51
+
52
+ app = FastAPI(
53
+ title="Universal Deep Research Backend API",
54
+ description="Intelligent research and reporting service using LLMs and web search",
55
+ version="1.0.0",
56
+ )
57
+
58
+ # Configure logging
59
+ LOGGING_CONFIG["formatters"]["default"][
60
+ "fmt"
61
+ ] = "%(asctime)s [%(name)s] %(levelprefix)s %(message)s"
62
+
63
+ # Configure CORS
64
+ app.add_middleware(
65
+ CORSMiddleware,
66
+ allow_origins=[config.cors.frontend_url], # Frontend URL from config
67
+ allow_credentials=config.cors.allow_credentials,
68
+ allow_methods=config.cors.allow_methods,
69
+ allow_headers=config.cors.allow_headers,
70
+ )
71
+
72
+
73
+ class Message(BaseModel):
74
+ text: str
75
+
76
+
77
+ class ResearchRequest(BaseModel):
78
+ dry: bool = False
79
+ session_key: Optional[str] = None
80
+ start_from: str = "research"
81
+ strategy_id: Optional[str] = None
82
+ strategy_content: Optional[str] = None
83
+ prompt: Optional[str] = None
84
+ mock_directory: str = "mock_instances/stocks_24th_3_sections"
85
+
86
+
87
+ @app.get("/")
88
+ async def root():
89
+ return {
90
+ "message": "The Deep Research Backend is running. Use the /api/research endpoint to start a new research session."
91
+ }
92
+
93
+
94
+ def build_events_path(session_key: str) -> str:
95
+ return f"instances/{session_key}.events.jsonl"
96
+
97
+
98
+ def make_message(
99
+ event: Dict[str, Any],
100
+ session_key: str | None = None,
101
+ timestamp_the_event: bool = True,
102
+ ) -> str:
103
+ if timestamp_the_event:
104
+ event = {**event, "timestamp": datetime.now().isoformat()}
105
+
106
+ if session_key:
107
+ items.register_item(build_events_path(session_key), event)
108
+
109
+ return json.dumps({"event": event, "session_key": session_key}) + "\n"
110
+
111
+
112
+ @app.post("/api/research")
113
+ async def start_research(request: ResearchRequest):
114
+ """
115
+ Start or continue a research process and stream the results using JSON streaming.
116
+
117
+ This endpoint initiates a comprehensive research workflow that includes:
118
+ - Query analysis and topic extraction
119
+ - Web search using Tavily API
120
+ - Content filtering and relevance scoring
121
+ - Report generation using LLMs
122
+
123
+ The response is streamed as Server-Sent Events (SSE) with real-time progress updates.
124
+
125
+ Args:
126
+ request (ResearchRequest): The research request containing:
127
+ - dry (bool): Use mock data for testing (default: False)
128
+ - session_key (str, optional): Existing session to continue
129
+ - start_from (str): "research" or "reporting" phase
130
+ - prompt (str): Research query (required for research phase)
131
+ - mock_directory (str): Directory for mock data
132
+
133
+ Returns:
134
+ StreamingResponse: Server-Sent Events stream with research progress
135
+
136
+ Raises:
137
+ HTTPException: 400 if request parameters are invalid
138
+
139
+ Example:
140
+ ```bash
141
+ curl -X POST http://localhost:8000/api/research \\
142
+ -H "Content-Type: application/json" \\
143
+ -d '{
144
+ "prompt": "What are the latest developments in quantum computing?",
145
+ "start_from": "research"
146
+ }'
147
+ ```
148
+ """
149
+ # Validate request parameters
150
+ if request.start_from not in ["research", "reporting"]:
151
+ raise HTTPException(
152
+ status_code=400,
153
+ detail="start_from must be either 'research' or 'reporting'",
154
+ )
155
+
156
+ if request.start_from == "reporting" and not request.session_key:
157
+ raise HTTPException(
158
+ status_code=400,
159
+ detail="session_key is required when starting from reporting phase",
160
+ )
161
+
162
+ if request.start_from == "research" and not request.prompt:
163
+ raise HTTPException(
164
+ status_code=400,
165
+ detail="prompt is required when starting from research phase",
166
+ )
167
+
168
+ # Use configured mock directory
169
+ mock_dir = request.mock_directory or config.research.mock_directory
170
+
171
+ # Choose implementation
172
+ research_impl = (
173
+ (lambda session_key, prompt: dry_research(session_key, prompt, mock_dir))
174
+ if request.dry
175
+ else real_research
176
+ )
177
+ reporting_impl = (
178
+ (lambda session_key: dry_reporting(session_key, mock_dir))
179
+ if request.dry
180
+ else real_reporting
181
+ )
182
+
183
+ # Generate or use provided session key
184
+ session_key = request.session_key or generate_session_key()
185
+
186
+ # Prepare generators
187
+ research_gen = (
188
+ research_impl(session_key, request.prompt)
189
+ if request.start_from == "research"
190
+ else None
191
+ )
192
+ reporting_gen = reporting_impl(session_key)
193
+
194
+ return StreamingResponse(
195
+ stream_research_events(
196
+ research_gen, reporting_gen, request.start_from == "research", session_key
197
+ ),
198
+ media_type="application/x-ndjson",
199
+ headers={
200
+ "Cache-Control": "no-cache",
201
+ "Connection": "keep-alive",
202
+ "Content-Encoding": "none",
203
+ },
204
+ )
205
+
206
+
207
+ async def stream_research_events(
208
+ research_fn: AsyncGenerator[Dict[str, Any], None],
209
+ reporting_fn: AsyncGenerator[Dict[str, Any], None],
210
+ do_research: bool,
211
+ session_key: str,
212
+ ) -> AsyncGenerator[str, None]:
213
+ """
214
+ Stream research or reporting events using JSON streaming format.
215
+
216
+ Args:
217
+ research_fn: Research phase generator
218
+ reporting_fn: Reporting phase generator
219
+ do_research: Whether to run research phase
220
+ session_key: Session identifier
221
+
222
+ Yields:
223
+ JSON formatted event strings, one per line
224
+ """
225
+ try:
226
+ yield make_message(
227
+ {
228
+ "type": "started",
229
+ "description": "Waking up the Deep Research Backend",
230
+ },
231
+ session_key,
232
+ )
233
+
234
+ error_event_encountered: bool = False
235
+ if do_research:
236
+ async for event in research_fn:
237
+ if event["type"] == "error":
238
+ error_event_encountered = True
239
+ yield make_message(event, session_key)
240
+
241
+ if not error_event_encountered:
242
+ async for event in reporting_fn:
243
+ yield make_message(event, session_key)
244
+
245
+ # Send completion message
246
+ yield make_message(
247
+ {
248
+ "type": "completed",
249
+ "description": "Research and reporting completed",
250
+ },
251
+ session_key,
252
+ )
253
+ except asyncio.CancelledError:
254
+ # Send cancellation message before propagating the exception
255
+ yield make_message(
256
+ {
257
+ "type": "cancelled",
258
+ "description": "Research was cancelled",
259
+ },
260
+ session_key,
261
+ )
262
+ raise
263
+
264
+
265
+ @app.post("/api/research2")
266
+ async def start_research2(request: ResearchRequest):
267
+ # Validate request parameters
268
+ if request.start_from not in ["research"]:
269
+ raise HTTPException(status_code=400, detail="start_from must be 'research'")
270
+
271
+ if request.start_from == "research" and not request.prompt:
272
+ raise HTTPException(
273
+ status_code=400,
274
+ detail="prompt is required when starting from research phase",
275
+ )
276
+
277
+ # Generate or use provided session key
278
+ session_key = generate_session_key()
279
+
280
+ if request.strategy_id is None or request.strategy_id == "default":
281
+ # Validate request parameters
282
+ if request.start_from not in ["research", "reporting"]:
283
+ raise HTTPException(
284
+ status_code=400,
285
+ detail="start_from must be either 'research' or 'reporting'",
286
+ )
287
+
288
+ if request.start_from == "reporting" and not request.session_key:
289
+ raise HTTPException(
290
+ status_code=400,
291
+ detail="session_key is required when starting from reporting phase",
292
+ )
293
+
294
+ if request.start_from == "research" and not request.prompt:
295
+ raise HTTPException(
296
+ status_code=400,
297
+ detail="prompt is required when starting from research phase",
298
+ )
299
+
300
+ # Choose implementation
301
+ research_impl = (
302
+ (
303
+ lambda session_key, prompt: dry_research(
304
+ session_key, prompt, "mock_instances/stocks_24th_3_sections"
305
+ )
306
+ )
307
+ if request.dry
308
+ else real_research
309
+ )
310
+ reporting_impl = (
311
+ (
312
+ lambda session_key: dry_reporting(
313
+ session_key, "mock_instances/stocks_24th_3_sections"
314
+ )
315
+ )
316
+ if request.dry
317
+ else real_reporting
318
+ )
319
+
320
+ # Generate or use provided session key
321
+ session_key = request.session_key or generate_session_key()
322
+
323
+ # Prepare generators
324
+ research_gen = (
325
+ research_impl(session_key, request.prompt)
326
+ if request.start_from == "research"
327
+ else None
328
+ )
329
+ reporting_gen = reporting_impl(session_key)
330
+
331
+ return StreamingResponse(
332
+ stream_research_events(
333
+ research_gen,
334
+ reporting_gen,
335
+ request.start_from == "research",
336
+ session_key,
337
+ ),
338
+ media_type="application/x-ndjson",
339
+ headers={
340
+ "Cache-Control": "no-cache",
341
+ "Connection": "keep-alive",
342
+ "Content-Encoding": "none",
343
+ },
344
+ )
345
+
346
+ return StreamingResponse(
347
+ stream_research2_events(
348
+ session_key, request.prompt, request.strategy_id, request.strategy_content
349
+ ),
350
+ media_type="application/x-ndjson",
351
+ headers={
352
+ "Cache-Control": "no-cache",
353
+ "Connection": "keep-alive",
354
+ "Content-Encoding": "none",
355
+ },
356
+ )
357
+
358
+
359
+ async def stream_research2_events(
360
+ session_key: str, prompt: str, strategy_id: str, strategy_content: str
361
+ ) -> AsyncGenerator[str, None]:
362
+ try:
363
+ yield make_message(
364
+ {
365
+ "type": "started",
366
+ "description": "Waking up the Universal Deep Research Backend",
367
+ },
368
+ session_key,
369
+ )
370
+
371
+ # Set the random seed from configuration
372
+ random.seed(config.research.random_seed)
373
+
374
+ # Set trace filename using configuration
375
+ comm_trace_timestamp: str = datetime.now().strftime("%Y%m%d_%H-%M-%S")
376
+ comm_trace_filename = (
377
+ f"{config.logging.log_dir}/comms_{comm_trace_timestamp}.log"
378
+ )
379
+ comm_trace = Trace(
380
+ comm_trace_filename, copy_into_stdout=config.logging.copy_into_stdout
381
+ )
382
+
383
+ client: Client = OpenAIClient(
384
+ base_url="https://integrate.api.nvidia.com/v1",
385
+ model="nvdev/meta/llama-3.1-70b-instruct",
386
+ trace=comm_trace,
387
+ )
388
+
389
+ frame_config = FrameConfigV4(
390
+ long_context_cutoff=config.frame.long_context_cutoff,
391
+ force_long_context=config.frame.force_long_context,
392
+ max_iterations=config.frame.max_iterations,
393
+ interaction_level=config.frame.interaction_level,
394
+ )
395
+ harness = FrameV4(
396
+ client_profile=client,
397
+ errand_profile={},
398
+ compilation_trace=True,
399
+ execution_trace="file_and_stdout",
400
+ )
401
+
402
+ messages = []
403
+ preamble_files = [
404
+ "frame/prompts/udr_minimal_generating/0.code_skill.py",
405
+ ]
406
+ for path in preamble_files:
407
+ type = path.split(".")[-2]
408
+ with open(path, "r") as f:
409
+ messages.append(
410
+ {
411
+ "mid": len(messages),
412
+ "role": "user",
413
+ "content": f.read(),
414
+ "type": type,
415
+ }
416
+ )
417
+
418
+ messages.append(
419
+ {
420
+ "mid": len(messages),
421
+ "role": "user",
422
+ "content": "The following is the prompt data to be used in later procedures.\n\nPROMPT:\n"
423
+ + prompt,
424
+ "type": "data",
425
+ }
426
+ )
427
+
428
+ messages.append(
429
+ {
430
+ "mid": len(messages),
431
+ "role": "user",
432
+ "content": strategy_content,
433
+ "type": "generating_routine",
434
+ }
435
+ )
436
+
437
+ for i in range(len(messages)):
438
+ messages_so_far = messages[: i + 1]
439
+ yield make_message(
440
+ {
441
+ "type": "generic",
442
+ "description": "Processing agentic instructions: "
443
+ + str(i + 1)
444
+ + " of "
445
+ + str(len(messages)),
446
+ },
447
+ session_key,
448
+ )
449
+ for notification in harness.generate_with_notifications(
450
+ messages=messages_so_far,
451
+ frame_config=frame_config,
452
+ ):
453
+ yield make_message(notification, session_key)
454
+
455
+ yield make_message(
456
+ {
457
+ "type": "completed",
458
+ "description": "Research completed",
459
+ },
460
+ session_key,
461
+ )
462
+ except asyncio.CancelledError:
463
+ # Send cancellation message before propagating the exception
464
+ yield make_message(
465
+ {
466
+ "type": "cancelled",
467
+ "description": "Research was cancelled",
468
+ },
469
+ session_key,
470
+ )
471
+ raise
backend/requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-dotenv
4
+ attr
5
+ datasets
6
+ docstring_parser
7
+ evaluate
8
+ openai==1.65.1
9
+ transformers
10
+ tavily-python
backend/scan_research.py ADDED
@@ -0,0 +1,536 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ Core research and reporting module for the Universal Deep Research Backend (UDR-B).
17
+
18
+ This module provides the main research functionality including:
19
+ - Query analysis and topic extraction
20
+ - Web search using Tavily API
21
+ - Content filtering and relevance scoring
22
+ - Report generation using LLMs
23
+ """
24
+
25
+ import asyncio
26
+ import random
27
+ from typing import Any, AsyncGenerator, Dict, List
28
+
29
+ from openai import OpenAI
30
+ from tavily import TavilyClient
31
+
32
+ import items
33
+ from clients import (
34
+ create_lm_client,
35
+ create_tavily_client,
36
+ get_completion,
37
+ is_output_positive,
38
+ )
39
+ from config import get_config
40
+ from sessions import generate_session_key
41
+
42
+ # Get configuration
43
+ config = get_config()
44
+
45
+ # Use configuration values
46
+ MAX_TOPICS: int = config.research.max_topics
47
+ MAX_SEARCH_PHRASES: int = config.research.max_search_phrases
48
+
49
+
50
+ def build_research_artifacts_path(session_key: str) -> str:
51
+ return f"instances/{session_key}.research_artifacts.jsonl"
52
+
53
+
54
+ def build_reporting_artifacts_path(session_key: str) -> str:
55
+ return f"instances/{session_key}.reporting_artifacts.jsonl"
56
+
57
+
58
+ def make_event(
59
+ type: str,
60
+ description: str,
61
+ hidden: bool = False,
62
+ deltaSearchCount: int = 0,
63
+ deltaQueryCount: int = 0,
64
+ report: str | None = None,
65
+ ) -> Dict[str, Any]:
66
+ return {
67
+ "type": type,
68
+ "description": description,
69
+ "hidden": hidden,
70
+ "deltaSearchCount": deltaSearchCount,
71
+ "deltaQueryCount": deltaQueryCount,
72
+ **({"report": report} if report is not None else {}),
73
+ }
74
+
75
+
76
+ async def do_research(
77
+ session_key: str, prompt: str
78
+ ) -> AsyncGenerator[Dict[str, Any], None]:
79
+ """
80
+ Performs research based on the provided query and yields events as they occur.
81
+
82
+ This function handles the research phase, including:
83
+ - Receipt and analysis of the research query
84
+ - Planning the research approach
85
+ - Searching for relevant information
86
+ - Filtering and categorizing results
87
+ - Aggregating findings into a coherent output
88
+
89
+ Events yielded:
90
+ - prompt_received: Initial receipt of the research query
91
+ - prompt_analysis: Analysis of the query's requirements
92
+ - research_planning: Planning the research approach
93
+ - topic_exploration_started: Starting research on each topic
94
+ - search_started: Initiating a search for each search phrase
95
+ - search_result_processing_started: Processing the search results
96
+ - aggregation_started: Beginning the aggregation of relevant segments
97
+ - research_completed: Completion of the research phase
98
+
99
+ Args:
100
+ session_key (str): A unique identifier for the research session.
101
+ prompt (str): The research query to process.
102
+
103
+ Yields:
104
+ Dict containing:
105
+ - type (str): The event type.
106
+ - description (str): User-friendly description of the event.
107
+ - hidden (bool, default=False): Whether the event is hidden from the user or should be displayed.
108
+ - deltaSearchCount (int, optional): The number of search queries performed.
109
+ - deltaQueryCount (int, optional): The number of queries performed.
110
+ - report (str, optional): The report produced.
111
+ """
112
+
113
+ research_artifacts_path: str = build_research_artifacts_path(session_key)
114
+ items.register_item(research_artifacts_path, {"type": "prompt", "prompt": prompt})
115
+
116
+ client = create_lm_client()
117
+ tavily_client = create_tavily_client()
118
+
119
+ yield make_event(
120
+ "prompt_received",
121
+ f"Received research request: '{prompt}'",
122
+ )
123
+
124
+ prompt_valid: bool = check_if_prompt_is_valid(client, prompt)
125
+ items.register_item(
126
+ research_artifacts_path, {"type": "prompt_validity", "valid": prompt_valid}
127
+ )
128
+ if not prompt_valid:
129
+ yield make_event(
130
+ "error",
131
+ "It would appear that the prompt is not a valid document research prompt. Please try again with a valid prompt.",
132
+ deltaQueryCount=1,
133
+ )
134
+ return
135
+
136
+ yield {
137
+ "type": "prompt_analysis_started",
138
+ "description": "Analyzing the research request...",
139
+ "hidden": False,
140
+ }
141
+
142
+ task_prompt, format_prompt = perform_prompt_decomposition(client, prompt)
143
+ items.register_item(
144
+ research_artifacts_path, {"type": "task_prompt", "task_prompt": task_prompt}
145
+ )
146
+ items.register_item(
147
+ research_artifacts_path,
148
+ {"type": "format_prompt", "format_prompt": format_prompt},
149
+ )
150
+
151
+ yield make_event(
152
+ "prompt_analysis_completed",
153
+ f"Prompt analysis completed. Will analyze the following task assignment: '{task_prompt}'.",
154
+ deltaQueryCount=1,
155
+ )
156
+
157
+ topics: List[str] = generate_topics(client, task_prompt)
158
+ items.register_item(research_artifacts_path, {"type": "topics", "topics": topics})
159
+ yield make_event(
160
+ "task_analysis_completed",
161
+ f"Task analysis completed. Will be researching {len(topics)}+ topics.",
162
+ deltaQueryCount=1,
163
+ )
164
+
165
+ topic_relevant_segments: dict[str, List[str]] = {}
166
+ search_result_urls: List[str] = []
167
+ all_results: List[Dict[str, Any]] = []
168
+ for topic in topics:
169
+ yield make_event(
170
+ "topic_exploration_started",
171
+ f"Researching '{topic}'",
172
+ )
173
+
174
+ search_phrases: List[str] = produce_search_phrases(client, prompt, topic)
175
+ items.register_item(
176
+ research_artifacts_path,
177
+ {
178
+ "type": "topic_search_phrases",
179
+ "topic": topic,
180
+ "search_phrases": search_phrases,
181
+ },
182
+ )
183
+
184
+ yield make_event(
185
+ "topic_exploration_completed",
186
+ f"Will invoke {len(search_phrases)} search phrases to research '{topic}'.",
187
+ deltaQueryCount=1,
188
+ )
189
+
190
+ topic_relevant_segments[topic] = []
191
+ for search_phrase in search_phrases:
192
+ yield make_event(
193
+ "search_started",
194
+ f"Searching for '{search_phrase}'",
195
+ )
196
+
197
+ search_results: List[Dict[str, Any]] = perform_search(
198
+ tavily_client, search_phrase, research_artifacts_path
199
+ )
200
+ items.register_item(
201
+ research_artifacts_path,
202
+ {
203
+ "type": "topic_search_results",
204
+ "topic": topic,
205
+ "search_phrase": search_phrase,
206
+ "results": search_results,
207
+ },
208
+ )
209
+ original_search_result_urls: List[str] = [
210
+ result["url"]
211
+ for result in search_results
212
+ if result["url"] not in search_result_urls
213
+ ]
214
+ search_result_urls.extend(original_search_result_urls)
215
+ original_search_results = [
216
+ result
217
+ for result in search_results
218
+ if result["url"] in original_search_result_urls
219
+ ]
220
+ all_results.extend(original_search_results)
221
+
222
+ yield make_event(
223
+ "search_result_processing_started",
224
+ f"Processing {len(original_search_result_urls)} search results.",
225
+ deltaSearchCount=1,
226
+ )
227
+
228
+ for search_result in original_search_results:
229
+ search_result_content: str = search_result["raw_content"]
230
+ search_result_url: str = search_result["url"]
231
+ search_result_url_index: int = search_result_urls.index(
232
+ search_result_url
233
+ )
234
+ relevant_segments: List[str] = find_relevant_segments(
235
+ client,
236
+ prompt,
237
+ topic,
238
+ search_result_content,
239
+ search_result_url_index,
240
+ )
241
+ topic_relevant_segments[topic].extend(relevant_segments)
242
+ items.register_item(
243
+ research_artifacts_path,
244
+ {
245
+ "type": "topic_search_result_relevant_segments",
246
+ "topic": topic,
247
+ "search_phrase": search_phrase,
248
+ "search_result": search_result,
249
+ "relevant_segments": relevant_segments,
250
+ },
251
+ )
252
+
253
+ yield make_event(
254
+ "search_result_processing_completed",
255
+ f"Processed search result {search_result_url_index}.",
256
+ hidden=True,
257
+ deltaQueryCount=1,
258
+ )
259
+
260
+ yield make_event(
261
+ "aggregation_started",
262
+ "Aggregating relevant information for all topics.",
263
+ )
264
+
265
+ items.register_item(
266
+ research_artifacts_path,
267
+ {
268
+ "type": "topic_relevant_segments",
269
+ "topic_relevant_segments": topic_relevant_segments,
270
+ "search_result_urls": search_result_urls,
271
+ },
272
+ )
273
+ items.register_item(
274
+ research_artifacts_path, {"type": "all_results", "all_results": all_results}
275
+ )
276
+
277
+ # sleep for 0.5s
278
+ await asyncio.sleep(0.5)
279
+
280
+ yield make_event(
281
+ "research_completed",
282
+ "Research phase completed.",
283
+ )
284
+
285
+
286
+ async def do_reporting(session_key: str) -> AsyncGenerator[Dict[str, Any], None]:
287
+ """
288
+ Generates a report based on the research findings and yields events as they occur.
289
+
290
+ This function handles the reporting phase, including:
291
+ - Report construction
292
+ - Formatting and organization
293
+ - Final delivery
294
+
295
+ Events yielded:
296
+ - report_building: Initial report construction
297
+ - report_formatting: Formatting and structuring the report
298
+ - report_done: Report completion and delivery
299
+
300
+ Yields:
301
+ Dict containing:
302
+ - type (str): The event type
303
+ - description (str): User-friendly description of the event
304
+ - hidden (bool, default=False): Whether the event is hidden from the user or should be displayed
305
+ - deltaSearchCount (int, optional): The number of search queries performed
306
+ - deltaQueryCount (int, optional): The number of queries performed
307
+ - report (str, optional): The report produced
308
+ """
309
+
310
+ client = create_lm_client()
311
+
312
+ research_artifacts_path: str = build_research_artifacts_path(session_key)
313
+ task_prompt: str = items.find_item_by_type(research_artifacts_path, "task_prompt")[
314
+ "task_prompt"
315
+ ]
316
+ format_prompt: str = items.find_item_by_type(
317
+ research_artifacts_path, "format_prompt"
318
+ )["format_prompt"]
319
+ topic_relevant_segments: dict[str, List[str]] = items.find_item_by_type(
320
+ research_artifacts_path, "topic_relevant_segments"
321
+ )["topic_relevant_segments"]
322
+ search_result_urls: List[str] = items.find_item_by_type(
323
+ research_artifacts_path, "topic_relevant_segments"
324
+ )["search_result_urls"]
325
+ all_results: List[Dict[str, Any]] = items.find_item_by_type(
326
+ research_artifacts_path, "all_results"
327
+ )["all_results"]
328
+
329
+ yield make_event(
330
+ "report_building",
331
+ "Building the report...",
332
+ )
333
+
334
+ reporting_artifacts_path: str = build_reporting_artifacts_path(session_key)
335
+ report: str = produce_report(
336
+ client, task_prompt, format_prompt, topic_relevant_segments
337
+ )
338
+ items.register_item(reporting_artifacts_path, {"type": "report", "report": report})
339
+
340
+ # Step 3: Ensure that the report is consistent and formatted correctly in Markdown
341
+ yield make_event(
342
+ "report_processing",
343
+ "Formatting the report...",
344
+ deltaQueryCount=1,
345
+ )
346
+ consistent_report: str = ensure_format_is_respected(
347
+ client, task_prompt, format_prompt, report
348
+ )
349
+ consistent_report += "\n\n"
350
+ consistent_report += "---\n"
351
+ for search_result_url_index, search_result_url in enumerate(search_result_urls):
352
+ result = next(
353
+ (result for result in all_results if result["url"] == search_result_url),
354
+ None,
355
+ )
356
+ consistent_report += f" - [[{search_result_url_index}]] [{result['title']}][{search_result_url_index}]\n"
357
+ consistent_report += "\n\n"
358
+ for search_result_url_index, search_result_url in enumerate(search_result_urls):
359
+ consistent_report += f"[{search_result_url_index}]: {search_result_url}\n"
360
+ items.register_item(
361
+ reporting_artifacts_path,
362
+ {"type": "consistent_report", "consistent_report": consistent_report},
363
+ )
364
+
365
+ yield make_event(
366
+ "report_done",
367
+ "Report completed.",
368
+ report=consistent_report,
369
+ deltaQueryCount=1,
370
+ )
371
+
372
+
373
+ # ERRANDS
374
+ def check_if_prompt_is_valid(client: OpenAI, prompt: str) -> bool:
375
+ messages = [
376
+ {
377
+ "role": "system",
378
+ "content": "You are a helpful assistant that checks if a prompt is a valid deep information research prompt. A valid prompt is in English and gives a task to research one or more topics and produce a report. Invalid prompts are general language model prompts that ask simple (perhaps even yes or no) questions, ask forexplanations, or attempt to have a conversation. Examples of valid prompts: 'What was the capital of France in 1338?', 'Write a report on stock market situation on during this morning', 'Produce a thorough report on the major event happened in the Christian world on the 21st of April 2025.', 'Produce a report on the differences between the US and European economy health in 2024.', 'What is the short history of the internet?'. Examples of invalid prompts: 'Is the weather in Tokyo good?', 'ppdafsfgr hdafdf', 'Hello, how are you?', 'The following is a code. Can you please explain it to me and then simulate it?'",
379
+ },
380
+ {
381
+ "role": "user",
382
+ "content": f"Is the following prompt a valid information research prompt? Respond with 'yes' or 'no'. Do not output any other text.\n\n{prompt}\n\n Reminders: Find out if the above-given prompt is a valid information research prompt. Do not output any other text.",
383
+ },
384
+ ]
385
+ return is_output_positive(get_completion(client, messages))
386
+
387
+
388
+ def perform_prompt_decomposition(client: OpenAI, prompt: str) -> List[str]:
389
+ messages = [
390
+ {
391
+ "role": "system",
392
+ "content": "You are a helpful assistant that decomposes a prompt into a task to be performed and a format in which the report should be produced. The output should be two prompts separated by a double-newline. The first prompt is the task to be performed, and the second prompt is the format in which the report should be produced. If there is no formatting constraint, output 'No formatting constraint' in the second prompt. Do not output any other text.",
393
+ },
394
+ {
395
+ "role": "user",
396
+ "content": f"Decompose the PROMPT into a task to be performed and a format in which the report should be produced. If there is no formatting constraint, output 'No formatting constraint' in the second prompt. Do not output any other text.\n\nEXAMPLE PROMPT:\nWrite a three-chapter report on the differences between the US and European economy health in 2024. The first chapter should be about the US economy health, the second chapter should be about the European economy health, and the third chapter should be about the differences between the two.\n\nEXAMPLE OUTPUT:\nWrite a report on the differences between the US and European economy health in 2024.\n\nThe report should be in the form of a three-chapter report. The first chapter should be about the US economy health, the second chapter should be about the European economy health, and the third chapter should be about the differences between the two.\n\nPROMPT: {prompt}\n\nReminders: The output should be two prompts separated by a double-newline. The first prompt is the task to be performed, and the second prompt is the format in which the report should be produced. If there is no formatting constraint, output 'No formatting constraint' in the second prompt. Do not output any other text.",
397
+ },
398
+ ]
399
+ decomposition = get_completion(client, messages).split("\n\n")
400
+ if len(decomposition) != 2:
401
+ raise ValueError(
402
+ f"Failed to perform prompt decomposition; decomposition: {decomposition}"
403
+ )
404
+ return decomposition
405
+
406
+
407
+ def generate_topics(client: OpenAI, prompt: str) -> List[str]:
408
+ messages = [
409
+ {
410
+ "role": "system",
411
+ "content": f"You are a helpful assistant that decomposes a prompt into a list of topics to research. The output should be a list of strings separated by newlines, each representing a topic to research. The topics should be in English and should be specific and focused. Output at most {MAX_TOPICS} topics. Examples:\n\nPrompt: What was the capital of France in 1338?\nThe capital and seat of government of France in 1338\n\nPrompt: Produce a report on the differences between the US and European economy health in 2024\nUS economy health in 2024\nEuropean economy health in 2024\nGeneral differences between the US and European economy health in 2024\n\nPrompt: What is the short history of the internet?:\nThe history of the internet\n\nPrompt: Report on US crude oil prices in relation to Gold prices in 1970-1980\nUS crude oil prices in 1970-1980\nGold prices in 1970-1980\nGold-crude oil correlation in 1970-1980",
412
+ },
413
+ {
414
+ "role": "user",
415
+ "content": f"Decompose the following prompt into a list of topics to research:\n\nPrompt: {prompt}\n\nReminders: The output should be a list of strings separated by newlines, each representing a topic to research. The topics should be in English and should be specific and focused. Do not output any other text. Output at most {MAX_TOPICS} topics.",
416
+ },
417
+ ]
418
+ completion: str = get_completion(client, messages)
419
+ completion_lines: List[str] = completion.split("\n")
420
+ ret = [line.strip() for line in completion_lines if line.strip()]
421
+ if len(ret) > MAX_TOPICS:
422
+ ret = random.sample(ret, MAX_TOPICS)
423
+ return ret
424
+
425
+
426
+ def produce_search_phrases(client: OpenAI, prompt: str, topic: str) -> List[str]:
427
+ messages = [
428
+ {
429
+ "role": "system",
430
+ "content": f"You are a helpful assistant that produces a list of search phrases for a given topic. The output should be a newline-separated list of short search phrases that can be used in e.g. Google or Bing search engines. Output at most {MAX_SEARCH_PHRASES} search phrases. Examples:\n\nTopic: The capital and seat of government of France in 1338\nSearch phrases: The capital of France in 1338, The seat of government of France in 1338, Government of France in 1338 century, Government of France in the 14th century\n\nTopic: US crude oil prices in relation to Gold prices in 1970-1980\nSearch phrases: US crude oil prices in 1970-1980, Gold prices in 1970-1980, Gold-crude oil correlation in 1970-1980, Gold-crude oil correlation\n\nTopic: {topic}\nSearch phrases:",
431
+ },
432
+ {
433
+ "role": "user",
434
+ "content": f"Produce a list of search phrases for the following topic:\n\nPrompt (added for context): {prompt}\n\nTopic: {topic}\n\nReminders: The output should be a list of search phrases for the given topic separated by newlines. The search phrases should be in English and should be specific and focused. Output at most {MAX_SEARCH_PHRASES} search phrases. Do not output any other text.",
435
+ },
436
+ ]
437
+ completion: str = get_completion(client, messages)
438
+ completion_lines: List[str] = completion.split("\n")
439
+ ret = [line.strip() for line in completion_lines if line.strip()]
440
+ if len(ret) > MAX_SEARCH_PHRASES:
441
+ ret = random.sample(ret, MAX_SEARCH_PHRASES)
442
+ return ret
443
+
444
+
445
+ def perform_search(
446
+ client: TavilyClient, search_phrase: str, research_artifacts_path: str
447
+ ) -> List[Dict[str, Any]]:
448
+ search_response: Dict[str, Any] = client.search(
449
+ search_phrase, include_raw_content=True
450
+ )
451
+ items.register_item(
452
+ research_artifacts_path,
453
+ {
454
+ "type": "search_response_raw",
455
+ "search_phrase": search_phrase,
456
+ "response": search_response,
457
+ },
458
+ )
459
+ filtered_results: List[Dict[str, Any]] = [
460
+ result
461
+ for result in search_response["results"]
462
+ if result["raw_content"] is not None and result["raw_content"].strip() != ""
463
+ ]
464
+ items.register_item(
465
+ research_artifacts_path,
466
+ {
467
+ "type": "search_response_filtered",
468
+ "search_phrase": search_phrase,
469
+ "response": filtered_results,
470
+ },
471
+ )
472
+ return filtered_results
473
+
474
+
475
+ def find_relevant_segments(
476
+ client: OpenAI,
477
+ prompt: str,
478
+ topic: str,
479
+ search_result: str,
480
+ search_result_url_index: int,
481
+ ) -> List[str]:
482
+ messages = [
483
+ {
484
+ "role": "system",
485
+ "content": "You are a helpful assistant that finds relevant paragraphs in a given search result. The output should be a double-newline-separated list of relevant paragraphs. A paragraph can be a couple of sentences to dozens of sentences if they are really relevant. If there are no relevant paragraphs, just output an empty line or two and stop the generation. Do not output any other text.",
486
+ },
487
+ {
488
+ "role": "user",
489
+ "content": f"Find the sentences or paragraphs relevant to the following prompt in the following search result:\n\nSearch result: {search_result}\n\nPrompt (added for context): {prompt}\n\nTopic: {topic}\n\nReminders: The output should be a list of relevant paragraphs for the given topic separated by double-newlines. The relevant paragraphs should be in English and should be genuinely relevant to the prompt. Do not output any other text.",
490
+ },
491
+ ]
492
+ ret = get_completion(client, messages).split("\n")
493
+ ret = [line.strip() for line in ret if line.strip()]
494
+ ret = [f"[[{search_result_url_index}]] {paragraph}" for paragraph in ret]
495
+ return ret
496
+
497
+
498
+ def produce_report(
499
+ client: OpenAI,
500
+ prompt: str,
501
+ format_prompt: str,
502
+ topic_relevant_segments: dict[str, List[str]],
503
+ ) -> str:
504
+ topic_relevant_segments_str: str = "\n".join(
505
+ [
506
+ f"Topic: {topic}\n{'\n'.join(segments)}"
507
+ for topic, segments in topic_relevant_segments.items()
508
+ ]
509
+ )
510
+ messages = [
511
+ {
512
+ "role": "system",
513
+ "content": "You are a helpful assistant that produces a report based on the aggregated per-topic relevant paragraphs while citing sources. The output should be a report in Markdown format. The report should be self-consistent and formatted correctly in Markdown. Do not output any other text.",
514
+ },
515
+ {
516
+ "role": "user",
517
+ "content": f"Produce a report based on the following aggregated per-topic relevant paragraphs. Each paragraph contains an index of a source. Make sure to refer to this index in the form [[index]] every time you rely on the information from the source. Respect the format prompt. Do not output any other text.\n\nReport prompt: {prompt}\n\nTopic relevant paragraphs: {topic_relevant_segments_str}\n\nFormat prompt: {format_prompt}\n\nReminders: The output should be a report in Markdown format. The report should be formatted correctly according to the Format prompt in Markdown. Every single mention of an information stemming from one of the sources should be accompanied by the source index in the form [[index]] (or [[index1,index2,...]]) within or after the statement of the information. A list of the source URLs to correspond to the indices will be provided separately -- do not attempt to output it. Do not output any other text.",
518
+ },
519
+ ]
520
+ return get_completion(client, messages).strip()
521
+
522
+
523
+ def ensure_format_is_respected(
524
+ client: OpenAI, prompt: str, format_prompt: str, report: str
525
+ ) -> str:
526
+ messages = [
527
+ {
528
+ "role": "system",
529
+ "content": "You are a helpful assistant that ensures that a report is properly formatted. The output should be a report in Markdown format and follow the format prompt. The report should be formatted correctly in Markdown. Note that double horizontal rule (.e.g ==== etc.) are not supported in official Markdown. Do not output any other text.",
530
+ },
531
+ {
532
+ "role": "user",
533
+ "content": f"Ensure that the following report is properly formatted according to the format prompt. Do not output the Markdown output as code (i.e. enclosed in ```) -- just output the Markdown. Do not remove any references in the form [[index]] -- keep them in the text! The list of sources will be provided separately.\n\nReport: {report}\n\nFormat prompt: {format_prompt}\n\nReminders: The output should be a report in Markdown format. The report should be self-consistent and formatted correctly in Markdown. Do not output the Markdown output as code (i.e. enclosed in ```) -- just output the Markdown. Do not remove any references in the form [[index]] -- keep them in the text! The list of sources will be provided separately. Do not output any other text.",
534
+ },
535
+ ]
536
+ return get_completion(client, messages).strip()
backend/scan_research_dry.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import asyncio
16
+ import json
17
+ import random
18
+ from datetime import datetime
19
+ from glob import glob
20
+ from typing import Any, AsyncGenerator, Dict, List
21
+
22
+
23
+ async def do_research(
24
+ session_key: str,
25
+ query: str,
26
+ mock_directory: str = "mock_instances/stocks_24th_3_sections",
27
+ ) -> AsyncGenerator[Dict[str, Any], None]:
28
+ """
29
+ Generator that reads and replays events from mock_instances with random delays.
30
+ Does not save any artifacts or events.
31
+
32
+ Args:
33
+ query: The research query to process (ignored in dry mode)
34
+ session_key: Unique identifier for this research session (ignored in dry mode)
35
+ mock_directory: Directory containing mock instance files to replay
36
+
37
+ Yields:
38
+ Dict containing event data from the mock instance
39
+ """
40
+ # Find the mock instance files
41
+ mock_files = glob(f"{mock_directory}/*.events.jsonl")
42
+ if not mock_files:
43
+ yield {
44
+ "type": "error",
45
+ "description": "No mock instance files found in mock_instances directory",
46
+ }
47
+ return
48
+
49
+ # Use the first mock file found
50
+ mock_file = mock_files[0]
51
+
52
+ # Read all events from the mock file
53
+ with open(mock_file, "r") as f:
54
+ events = [json.loads(line) for line in f if line.strip()]
55
+
56
+ # Filter for non-reporting events
57
+ research_events = [
58
+ event
59
+ for event in events
60
+ if not event.get("type", "").startswith("report_")
61
+ and event.get("type", "") != "completed"
62
+ ]
63
+
64
+ # Replay each event with a random delay
65
+ for event in research_events:
66
+ # Update timestamp to current time
67
+ event["timestamp"] = datetime.now().isoformat()
68
+ # Yield the event
69
+ yield event
70
+ # Wait for a random time between 0.5 and 2 seconds
71
+ await asyncio.sleep(random.uniform(0.5, 2.0))
72
+
73
+
74
+ async def do_reporting(
75
+ session_key: str, mock_directory: str = "mock_instances/stocks_24th_3_sections"
76
+ ) -> AsyncGenerator[Dict[str, Any], None]:
77
+ """
78
+ Generator that reads and replays reporting events from mock_instances with random delays.
79
+ Does not save any artifacts or events.
80
+
81
+ Args:
82
+ session_key: Unique identifier for this research session (ignored in dry mode)
83
+ mock_directory: Directory containing mock instance files to replay
84
+
85
+ Yields:
86
+ Dict containing event data from the mock instance
87
+ """
88
+ # Find the mock instance files
89
+ mock_files = glob(f"{mock_directory}/*.events.jsonl")
90
+ if not mock_files:
91
+ yield {
92
+ "type": "error",
93
+ "description": "No mock instance files found in mock_instances directory",
94
+ }
95
+ return
96
+
97
+ # Use the first mock file found
98
+ mock_file = mock_files[0]
99
+
100
+ # Read all events from the mock file
101
+ with open(mock_file, "r") as f:
102
+ events = [json.loads(line) for line in f if line.strip()]
103
+
104
+ # Filter for reporting events
105
+ reporting_events = [
106
+ event for event in events if event.get("type", "").startswith("report_")
107
+ ]
108
+
109
+ # Replay each event with a random delay
110
+ for event in reporting_events:
111
+ # Update timestamp to current time
112
+ event["timestamp"] = datetime.now().isoformat()
113
+ # Yield the event
114
+ yield event
115
+ # Wait for a random time between 0.5 and 2 seconds
116
+ await asyncio.sleep(random.uniform(0.5, 2.0))
backend/sessions.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ Session management utilities for the Universal Deep Research Backend (UDR-B).
17
+
18
+ This module provides functions for generating unique session identifiers
19
+ that combine timestamps with random components for tracking research sessions.
20
+ """
21
+
22
+ import uuid
23
+ from datetime import datetime
24
+
25
+
26
+ def generate_session_key() -> str:
27
+ """
28
+ Generates a unique session key combining timestamp and random components.
29
+ Format: {timestamp}-{random_uuid}
30
+ Example: "20240315T123456Z-a1b2c3d4"
31
+
32
+ Returns:
33
+ str: A unique session identifier
34
+ """
35
+ timestamp = datetime.now().strftime("%Y%m%dT%H%M%SZ")
36
+ random_component = str(uuid.uuid4())[:8] # First 8 chars of UUID
37
+ return f"{timestamp}-{random_component}"
backend/setup.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #!/usr/bin/env python3
16
+ """
17
+ Setup script for the Universal Deep Research Backend (UDR-B).
18
+
19
+ This script helps users configure the backend by:
20
+ 1. Creating necessary directories
21
+ 2. Setting up environment configuration
22
+ 3. Validating API key files
23
+ 4. Installing dependencies
24
+ """
25
+
26
+ import os
27
+ import subprocess
28
+ import sys
29
+ from pathlib import Path
30
+
31
+
32
+ def print_header(title: str):
33
+ """Print a formatted header."""
34
+ print(f"\n{'='*60}")
35
+ print(f" {title}")
36
+ print(f"{'='*60}")
37
+
38
+
39
+ def print_step(step: str):
40
+ """Print a step message."""
41
+ print(f"\n→ {step}")
42
+
43
+
44
+ def check_python_version():
45
+ """Check if Python version is compatible."""
46
+ print_step("Checking Python version...")
47
+ if sys.version_info < (3, 8):
48
+ print("❌ Python 3.8 or higher is required")
49
+ sys.exit(1)
50
+ print(f"✅ Python {sys.version_info.major}.{sys.version_info.minor} detected")
51
+
52
+
53
+ def create_directories():
54
+ """Create necessary directories."""
55
+ print_step("Creating directories...")
56
+ directories = ["logs", "instances", "mock_instances"]
57
+
58
+ for directory in directories:
59
+ Path(directory).mkdir(exist_ok=True)
60
+ print(f"✅ Created directory: {directory}")
61
+
62
+
63
+ def setup_env_file():
64
+ """Set up environment configuration file."""
65
+ print_step("Setting up environment configuration...")
66
+
67
+ env_file = Path(".env")
68
+ env_example = Path("env.example")
69
+
70
+ if env_file.exists():
71
+ print("✅ .env file already exists")
72
+ return
73
+
74
+ if env_example.exists():
75
+ # Copy example file
76
+ with open(env_example, "r") as f:
77
+ content = f.read()
78
+
79
+ with open(env_file, "w") as f:
80
+ f.write(content)
81
+
82
+ print("✅ Created .env file from env.example")
83
+ print("⚠️ Please edit .env file with your configuration")
84
+ else:
85
+ print("❌ env.example file not found")
86
+ print("Creating basic .env file...")
87
+
88
+ basic_env = """# Universal Deep Research Backend (UDR-B) - Environment Configuration
89
+ HOST=0.0.0.0
90
+ PORT=8000
91
+ LOG_LEVEL=info
92
+ FRONTEND_URL=http://localhost:3000
93
+ DEFAULT_MODEL=llama-3.1-nemotron-253b
94
+ LLM_API_KEY_FILE=nvdev_api.txt
95
+ TAVILY_API_KEY_FILE=tavily_api.txt
96
+ MAX_TOPICS=1
97
+ MAX_SEARCH_PHRASES=1
98
+ LOG_DIR=logs
99
+ """
100
+ with open(env_file, "w") as f:
101
+ f.write(basic_env)
102
+
103
+ print("✅ Created basic .env file")
104
+ print("⚠️ Please edit .env file with your configuration")
105
+
106
+
107
+ def check_api_keys():
108
+ """Check for required API key files."""
109
+ print_step("Checking API key files...")
110
+
111
+ required_files = ["nvdev_api.txt", "tavily_api.txt"]
112
+
113
+ missing_files = []
114
+ for file in required_files:
115
+ if Path(file).exists():
116
+ print(f"✅ {file} found")
117
+ else:
118
+ print(f"❌ {file} missing")
119
+ missing_files.append(file)
120
+
121
+ if missing_files:
122
+ print(f"\n⚠️ Missing API key files: {', '.join(missing_files)}")
123
+ print("Please create these files with your API keys:")
124
+ for file in missing_files:
125
+ print(f" - {file}")
126
+ print("\nExample:")
127
+ print(" echo 'your-api-key-here' > nvdev_api.txt")
128
+ print(" echo 'your-tavily-key-here' > tavily_api.txt")
129
+
130
+
131
+ def install_dependencies():
132
+ """Install Python dependencies."""
133
+ print_step("Installing dependencies...")
134
+
135
+ try:
136
+ subprocess.run(
137
+ [sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
138
+ check=True,
139
+ capture_output=True,
140
+ text=True,
141
+ )
142
+ print("✅ Dependencies installed successfully")
143
+ except subprocess.CalledProcessError as e:
144
+ print(f"❌ Failed to install dependencies: {e}")
145
+ print("Please run: pip install -r requirements.txt")
146
+
147
+
148
+ def validate_setup():
149
+ """Validate the setup."""
150
+ print_step("Validating setup...")
151
+
152
+ # Check if main modules can be imported
153
+ try:
154
+ import clients
155
+ import config
156
+ import main
157
+ import scan_research
158
+
159
+ print("✅ All modules can be imported")
160
+ except ImportError as e:
161
+ print(f"❌ Import error: {e}")
162
+ return False
163
+
164
+ # Check if directories exist
165
+ required_dirs = ["logs", "instances"]
166
+ for directory in required_dirs:
167
+ if not Path(directory).exists():
168
+ print(f"❌ Directory missing: {directory}")
169
+ return False
170
+
171
+ print("✅ Setup validation passed")
172
+ return True
173
+
174
+
175
+ def main():
176
+ """Main setup function."""
177
+ print_header("Universal Deep Research Backend (UDR-B) Setup")
178
+
179
+ # Change to script directory
180
+ script_dir = Path(__file__).parent
181
+ os.chdir(script_dir)
182
+
183
+ check_python_version()
184
+ create_directories()
185
+ setup_env_file()
186
+ check_api_keys()
187
+ install_dependencies()
188
+
189
+ if validate_setup():
190
+ print_header("Setup Complete!")
191
+ print("🎉 Your Universal Deep Research Backend (UDR-B) is ready!")
192
+ print("\nNext steps:")
193
+ print("1. Edit .env file with your configuration")
194
+ print("2. Create API key files (nvdev_api.txt, tavily_api.txt)")
195
+ print("3. Run: ./launch_server.sh")
196
+ print("4. Or run: uvicorn main:app --reload")
197
+ print("\nFor more information, see README.md")
198
+ else:
199
+ print_header("Setup Failed")
200
+ print("❌ Please fix the issues above and run setup again")
201
+
202
+
203
+ if __name__ == "__main__":
204
+ main()
frontend/.gitignore ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # development logs
34
+ npm_run_dev.txt
35
+ uvicorn_main.txt
36
+
37
+ # env files (can opt-in for committing if needed)
38
+ .env*
39
+
40
+ # vercel
41
+ .vercel
42
+
43
+ # typescript
44
+ *.tsbuildinfo
45
+ next-env.d.ts
46
+
47
+
48
+ **/.DS_Store
frontend/CONTRIBUTING.md ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to Universal Deep Research Frontend
2
+
3
+ This code details a research and demonstration prototype. This software is not intended for production use.
4
+
5
+ ## How to Contribute
6
+
7
+ ### 1. Fork and Clone
8
+
9
+ ```bash
10
+ git clone <your-fork-url>
11
+ cd frontend
12
+ ```
13
+
14
+ ### 2. Set Up Development Environment
15
+
16
+ ```bash
17
+ npm install
18
+ # or
19
+ yarn install
20
+ ```
21
+
22
+ ### 3. Make Changes
23
+
24
+ - Follow existing code style and patterns
25
+ - Add tests for new functionality
26
+ - Update documentation as needed
27
+ - Ensure all TODO comments are addressed or documented
28
+
29
+ ### 4. Testing
30
+
31
+ - Test components manually in development mode
32
+ - Verify UI changes work correctly across different screen sizes
33
+ - Ensure accessibility standards are maintained
34
+
35
+ ### 5. Submit Pull Request
36
+
37
+ - Create a descriptive PR title
38
+ - Include clear description of changes
39
+ - Ensure code follows research/demonstration focus
40
+
41
+ ## Code Standards
42
+
43
+ - **TypeScript**: Follow TypeScript best practices and strict mode
44
+ - **React**: Use functional components with hooks
45
+ - **Next.js**: Follow Next.js 13+ App Router conventions
46
+ - **CSS**: Use CSS modules for component styling
47
+ - **Documentation**: Use clear JSDoc comments and inline documentation
48
+ - **Error Handling**: Implement proper error boundaries and error states
49
+ - **Configuration**: Use environment variables for customization
50
+ - **Accessibility**: Follow WCAG guidelines and use semantic HTML
51
+
52
+ ## Development Commands
53
+
54
+ ```bash
55
+ # Start development server
56
+ npm run dev
57
+ # or
58
+ yarn dev
59
+
60
+ # Build for production
61
+ npm run build
62
+ # or
63
+ yarn build
64
+
65
+ # Run linting
66
+ npm run lint
67
+ # or
68
+ yarn lint
69
+
70
+ # Run type checking
71
+ npm run type-check
72
+ # or
73
+ yarn type-check
74
+ ```
75
+
76
+ ## License
77
+
78
+ By contributing, you agree that your contributions will be licensed under the same terms as this project (research/demonstration use only). You can find the license in [LICENSE](../LICENSE.txt).
79
+
80
+ #### Signing Your Work
81
+
82
+ - We require that all contributors "sign-off" on their commits. This certifies that the contribution is your original work, or you have rights to submit it under the same license, or a compatible license.
83
+
84
+ - Any contribution which contains commits that are not Signed-Off will not be accepted.
85
+
86
+ - To sign off on a commit you simply use the `--signoff` (or `-s`) option when committing your changes:
87
+
88
+ ```bash
89
+ $ git commit -s -m "Add cool feature."
90
+ ```
91
+
92
+ This will append the following to your commit message:
93
+
94
+ ```
95
+ Signed-off-by: Your Name <[email protected]>
96
+ ```
97
+
98
+ - Full text of the DCO:
99
+
100
+ ```
101
+ Developer Certificate of Origin
102
+ Version 1.1
103
+
104
+ Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
105
+ 1 Letterman Drive
106
+ Suite D4700
107
+ San Francisco, CA, 94129
108
+
109
+ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
110
+ ```
111
+
112
+ ```
113
+ Developer's Certificate of Origin 1.1
114
+
115
+ By making a contribution to this project, I certify that:
116
+
117
+ (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or
118
+
119
+ (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or
120
+
121
+ (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.
122
+
123
+ (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved.
124
+ ```
frontend/DISCLAIMER.txt ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DISCLAIMER
2
+
3
+ IMPORTANT NOTICE - RESEARCH DEMONSTRATION PROTOTYPE
4
+
5
+ This software and all associated code, documentation, and materials (collectively, the "Software") are provided solely for research and demonstration purposes. The Software is intended exclusively as a prototype to demonstrate research concepts and methodologies in the field of artificial intelligence and automated research systems.
6
+
7
+ CRITICAL LIMITATIONS AND WARNINGS:
8
+
9
+ 1. RESEARCH PURPOSE ONLY: The Software is designed and intended solely for academic research, educational demonstration, and experimental purposes. It is NOT intended for production use, commercial deployment, or any real-world application.
10
+
11
+ 2. NO PRODUCTION USE: Under no circumstances should this Software be deployed, used, or relied upon in any production environment, commercial application, or any context where reliability, accuracy, or safety is required.
12
+
13
+ 3. EXPERIMENTAL NATURE: The Software contains experimental features, unproven methodologies, and research-grade implementations that may contain bugs, security vulnerabilities, or other issues that could cause data loss, system instability, or other problems.
14
+
15
+ 4. NO WARRANTIES: THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR ACCURACY.
16
+
17
+ 5. NO LIABILITY: TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, NEITHER NVIDIA CORPORATION, NOR THE AUTHORS, CONTRIBUTORS, OR ANY OTHER PARTIES INVOLVED IN THE CREATION, PRODUCTION, OR DELIVERY OF THE SOFTWARE SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
18
+
19
+ 6. NO SUPPORT: No technical support, maintenance, updates, or assistance is provided for this Software. Users are solely responsible for their use of the Software and any consequences thereof.
20
+
21
+ 7. NO ENDORSEMENT: The inclusion of any third-party components, APIs, or services does not constitute an endorsement of those services or their providers.
22
+
23
+ 8. COMPLIANCE: Users are responsible for ensuring their use of the Software complies with all applicable laws, regulations, and terms of service for any third-party services or APIs used by the Software.
24
+
25
+ 9. SECURITY: The Software has not been subjected to security audits or testing suitable for production environments. Users assume all risks associated with security vulnerabilities.
26
+
27
+ 10. DATA PRIVACY: The Software may process, transmit, or store data in ways that do not comply with data protection regulations. Users are responsible for ensuring compliance with applicable privacy laws.
28
+
29
+ ACKNOWLEDGMENT:
30
+
31
+ By using, copying, modifying, or distributing the Software, you acknowledge that you have read, understood, and agree to be bound by the terms of this disclaimer. If you do not agree to these terms, you must not use the Software.
32
+
33
+ For questions regarding this disclaimer, please contact the authors through the appropriate channels established for this research project.
34
+
35
+ Last updated: 07/15/2025.
frontend/README.md ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NVR Universal Deep Research - Frontend
2
+
3
+ A Next.js frontend application for the NVR Universal Deep Research system that provides an interactive interface for intelligent research and reporting capabilities.
4
+
5
+ This software is provided exclusively for research and demonstration purposes. It is intended solely as a prototype to demonstrate research concepts and methodologies in artificial intelligence and automated research systems.
6
+
7
+ - This software is not intended for production deployment, commercial use, or any real-world application where reliability, accuracy, or safety is required.
8
+ - This software contains experimental features, unproven methodologies, and research-grade implementations that may contain bugs, security vulnerabilities, or other issues.
9
+ - The software is provided "AS IS" without any warranties. Neither NVIDIA Corporation nor the authors shall be liable for any damages arising from the use of this software to the fullest extent permitted by law.
10
+
11
+ By using this software, you acknowledge that you have read and understood the complete DISCLAIMER file and agree to be bound by its terms. For the complete legal disclaimer, please see the [DISCLAIMER](DISCLAIMER.txt) file in this directory.
12
+
13
+ ## Features
14
+
15
+ - Interactive research interface with real-time progress tracking
16
+ - Configurable research strategies
17
+ - Support for both V1 and V2 API endpoints
18
+ - Dry run mode for testing
19
+ - Real-time report generation and viewing
20
+
21
+ ## Configuration
22
+
23
+ The frontend application is highly configurable through environment variables. Copy the example environment file and customize it for your deployment:
24
+
25
+ ```bash
26
+ cp env.example .env.local
27
+ ```
28
+
29
+ ### Environment Variables
30
+
31
+ #### Backend API Configuration
32
+
33
+ - `NEXT_PUBLIC_BACKEND_BASE_URL`: The base URL of your backend server (default: `http://localhost`)
34
+ - `NEXT_PUBLIC_BACKEND_PORT`: The port your backend server is running on (default: `8000`)
35
+ - `NEXT_PUBLIC_API_VERSION`: API version to use - `v1` or `v2` (default: `v2`)
36
+
37
+ #### Runtime Configuration
38
+
39
+ - `NEXT_PUBLIC_DRY_RUN`: Enable dry run mode - `true` or `false` (default: `false`)
40
+ - `NEXT_PUBLIC_ENABLE_V2_API`: Enable V2 API - `true` or `false` (default: `true`)
41
+
42
+ #### Frontend Configuration
43
+
44
+ - `NEXT_PUBLIC_FRONTEND_PORT`: The port for the frontend development server (default: `3000`)
45
+ - `NEXT_PUBLIC_FRONTEND_HOST`: The host for the frontend development server (default: `localhost`)
46
+
47
+ ### Example Configuration
48
+
49
+ For a production deployment with a backend on a different server:
50
+
51
+ ```bash
52
+ NEXT_PUBLIC_BACKEND_BASE_URL=http://your-backend-server.com
53
+ NEXT_PUBLIC_BACKEND_PORT=8000
54
+ NEXT_PUBLIC_API_VERSION=v2
55
+ NEXT_PUBLIC_DRY_RUN=false
56
+ NEXT_PUBLIC_ENABLE_V2_API=true
57
+ ```
58
+
59
+ For local development:
60
+
61
+ ```bash
62
+ NEXT_PUBLIC_BACKEND_BASE_URL=http://localhost
63
+ NEXT_PUBLIC_BACKEND_PORT=8000
64
+ NEXT_PUBLIC_API_VERSION=v2
65
+ NEXT_PUBLIC_DRY_RUN=true
66
+ NEXT_PUBLIC_ENABLE_V2_API=true
67
+ ```
68
+
69
+ ## Getting Started
70
+
71
+ 1. **Install dependencies:**
72
+
73
+ ```bash
74
+ npm install
75
+ ```
76
+
77
+ 2. **Configure environment variables:**
78
+
79
+ ```bash
80
+ cp env.example .env.local
81
+ # Edit .env.local with your configuration
82
+ ```
83
+
84
+ 3. **Run the development server:**
85
+
86
+ ```bash
87
+ npm run dev
88
+ ```
89
+
90
+ 4. **Open your browser:**
91
+ Navigate to [http://localhost:3000](http://localhost:3000) to see the application.
92
+
93
+ ## Available Scripts
94
+
95
+ - `npm run dev` - Start the development server with Turbopack
96
+ - `npm run build` - Build the application for production
97
+ - `npm run start` - Start the production server
98
+ - `npm run lint` - Run ESLint
99
+
100
+ ## Project Structure
101
+
102
+ ```
103
+ src/
104
+ ├── app/ # Next.js app directory
105
+ │ ├── page.tsx # Main application page
106
+ │ ├── layout.tsx # Root layout
107
+ │ └── globals.css # Global styles
108
+ ├── components/ # React components
109
+ │ ├── PromptBar.tsx # Main input interface
110
+ │ ├── ResearchProgressList.tsx # Progress tracking
111
+ │ ├── ReportViewer.tsx # Report display
112
+ │ └── ... # Other UI components
113
+ ├── config/ # Configuration management
114
+ │ └── index.ts # App configuration
115
+ └── types/ # TypeScript type definitions
116
+ └── ApplicationState.ts
117
+ ```
118
+
119
+ ## Backend Requirements
120
+
121
+ This frontend requires a compatible backend server running the NVR Universal Deep Research API. The backend should:
122
+
123
+ - Support both `/api/research` (V1) and `/api/research2` (V2) endpoints
124
+ - Accept POST requests with JSON payloads
125
+ - Return Server-Sent Events (SSE) for real-time progress updates
126
+ - Support the dry run mode parameter
127
+
128
+ ## Deployment
129
+
130
+ The application can be deployed to any platform that supports Next.js:
131
+
132
+ 1. Build the application: `npm run build`
133
+ 2. Start the production server: `npm run start`
134
+ 3. Set the appropriate environment variables for your deployment
135
+
136
+ ### Example Deployment Platforms
137
+
138
+ - **Vercel**: Connect your Git repository and set environment variables in the dashboard
139
+ - **Netlify**: Build and deploy with environment variable configuration
140
+ - **Railway**: Deploy with automatic environment variable management
141
+ - **Self-hosted**: Run on your own server with proper environment configuration
142
+
143
+ ## Troubleshooting
144
+
145
+ ### Common Issues
146
+
147
+ 1. **Backend Connection Errors**: Ensure your backend server is running and the `NEXT_PUBLIC_BACKEND_BASE_URL` and `NEXT_PUBLIC_BACKEND_PORT` are correctly configured.
148
+
149
+ 2. **CORS Errors**: Make sure your backend server allows requests from your frontend domain.
150
+
151
+ 3. **API Version Issues**: If you're experiencing API compatibility issues, try switching between `v1` and `v2` using the `NEXT_PUBLIC_API_VERSION` environment variable.
152
+
153
+ ### Development Tips
154
+
155
+ - Use `NEXT_PUBLIC_DRY_RUN=true` during development to avoid making actual API calls
156
+ - The application supports hot reloading - changes to your code will be reflected immediately
157
+ - Check the browser console for detailed error messages
158
+
159
+ ## Contributing
160
+
161
+ 1. Fork the repository
162
+ 2. Create a feature branch
163
+ 3. Make your changes
164
+ 4. Test thoroughly
165
+ 5. Submit a pull request
166
+
167
+ ## License and Disclaimer
168
+
169
+ This software is provided for research and demonstration purposes only. Please refer to the [DISCLAIMER](DISCLAIMER.txt) file for complete terms and conditions regarding the use of this software. You can find the license in [LICENSE](LICENSE.txt).
170
+
171
+ **Do not use this code in production.**
frontend/env.example ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backend API Configuration
2
+ # The base URL of your backend server (without protocol)
3
+ NEXT_PUBLIC_BACKEND_BASE_URL=http://localhost
4
+ # The port your backend server is running on
5
+ NEXT_PUBLIC_BACKEND_PORT=8000
6
+ # API version to use (v1 or v2)
7
+ NEXT_PUBLIC_API_VERSION=v2
8
+
9
+ # Runtime Configuration
10
+ # Enable dry run mode (true/false)
11
+ NEXT_PUBLIC_DRY_RUN=false
12
+ # Enable V2 API (true/false)
13
+ NEXT_PUBLIC_ENABLE_V2_API=true
14
+
15
+ # Frontend Configuration
16
+ # The port for the frontend development server
17
+ NEXT_PUBLIC_FRONTEND_PORT=3000
18
+ # The host for the frontend development server
19
+ NEXT_PUBLIC_FRONTEND_HOST=localhost
frontend/eslint.config.mjs ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { dirname } from "path";
2
+ import { fileURLToPath } from "url";
3
+ import { FlatCompat } from "@eslint/eslintrc";
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ const compat = new FlatCompat({
9
+ baseDirectory: __dirname,
10
+ });
11
+
12
+ const eslintConfig = [
13
+ ...compat.extends("next/core-web-vitals", "next/typescript"),
14
+ ];
15
+
16
+ export default eslintConfig;
frontend/launch_server.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ npm run dev > npm_run_dev.txt 2>&1 & disown $!
frontend/next.config.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { NextConfig } from "next";
18
+
19
+ const nextConfig: NextConfig = {
20
+ /* config options here */
21
+ };
22
+
23
+ export default nextConfig;