Spaces:
Paused
Paused
Upload 83 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- backend/.gitignore +54 -0
- backend/CHANGELOG.md +55 -0
- backend/CONTRIBUTING.md +96 -0
- backend/DISCLAIMER.txt +35 -0
- backend/README.md +406 -0
- backend/clients.py +249 -0
- backend/config.py +262 -0
- backend/env.example +47 -0
- backend/frame/__init__.py +14 -0
- backend/frame/clients.py +221 -0
- backend/frame/errands/message_code_call.txt +33 -0
- backend/frame/errands/message_code_processing.txt +154 -0
- backend/frame/errands/message_code_skill_processing.txt +136 -0
- backend/frame/errands/message_code_variables.txt +35 -0
- backend/frame/errands/message_data_processing.txt +129 -0
- backend/frame/errands/message_generating_routine_call.txt +33 -0
- backend/frame/errands/message_generating_routine_processing.txt +164 -0
- backend/frame/errands/message_routine_call.txt +33 -0
- backend/frame/errands/message_routine_processing.txt +164 -0
- backend/frame/errands/message_routine_variables.txt +36 -0
- backend/frame/errands/message_type.txt +81 -0
- backend/frame/errands4.py +188 -0
- backend/frame/harness4.py +775 -0
- backend/frame/prompts/udr_minimal/0.code_skill.py +40 -0
- backend/frame/prompts/udr_minimal/1.code_skill.py +34 -0
- backend/frame/prompts/udr_minimal/2.auto.txt +4 -0
- backend/frame/prompts/udr_minimal/3.auto.txt +11 -0
- backend/frame/prompts/udr_minimal_generating/0.code_skill.py +34 -0
- backend/frame/prompts/udr_minimal_generating/1.auto.txt +4 -0
- backend/frame/prompts/udr_minimal_generating/2.routine_generating.txt +15 -0
- backend/frame/routines.py +131 -0
- backend/frame/tidings.py +43 -0
- backend/frame/trace.py +51 -0
- backend/insert_license.py +70 -0
- backend/items.py +115 -0
- backend/launch_server.sh +49 -0
- backend/main.py +471 -0
- backend/requirements.txt +10 -0
- backend/scan_research.py +536 -0
- backend/scan_research_dry.py +116 -0
- backend/sessions.py +37 -0
- backend/setup.py +204 -0
- frontend/.gitignore +48 -0
- frontend/CONTRIBUTING.md +124 -0
- frontend/DISCLAIMER.txt +35 -0
- frontend/README.md +171 -0
- frontend/env.example +19 -0
- frontend/eslint.config.mjs +16 -0
- frontend/launch_server.sh +1 -0
- 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;
|