Spaces:
Runtime error
Runtime error
Deploy LaunchLLM - Production AI Training Platform
Browse files- .gitignore +10 -0
- README.md +170 -7
- app.py +10 -0
- backend_config.py +190 -0
- data/sample_financial_advisor_data.json +502 -0
- data/training_data_20251102_184524.json +0 -0
- data/training_data_20251102_184527.json +0 -0
- data/training_data_20251102_184642.json +0 -0
- data/training_data_20251102_185655.json +0 -0
- data/training_data_20251102_190750.json +0 -0
- data/training_data_20251102_193209.json +0 -0
- data/training_data_20251102_215614.json +487 -0
- data/training_data_20251102_220943.json +487 -0
- data_aggregation/__init__.py +28 -0
- data_aggregation/collectors.py +183 -0
- data_aggregation/dataset_builder.py +143 -0
- data_aggregation/hf_dataset_loader.py +528 -0
- data_aggregation/hf_dataset_loader.py.tmp.15260.1762137822282 +0 -0
- data_aggregation/preprocessors.py +98 -0
- data_aggregation/quality_validator.py +234 -0
- data_aggregation/synthetic_generator.py +234 -0
- data_aggregation/tool_use_generator.py +193 -0
- domain_templates/__init__.py +141 -0
- domain_templates/base_domain.py +93 -0
- domain_templates/custom_domain.py +156 -0
- domain_templates/education_tutor.py +221 -0
- domain_templates/financial_advisor.py +178 -0
- domain_templates/legal_advisor.py +181 -0
- domain_templates/medical_assistant.py +178 -0
- evaluation/__init__.py +43 -0
- evaluation/benchmark.py +368 -0
- evaluation/benchmark_builder.py +505 -0
- evaluation/certification_tests.py +500 -0
- evaluation/evaluator.py +347 -0
- evaluation/gap_analyzer.py +404 -0
- evaluation/intelligent_benchmark_scraper.py +474 -0
- evaluation/metrics.py +267 -0
- evaluation/training_recommender.py +505 -0
- financial_advisor_gui.py +2418 -0
- fine_tuning/__init__.py +12 -0
- fine_tuning/lora_trainer.py +487 -0
- fine_tuning/peft_trainer.py +149 -0
- fine_tuning/training_loop.py +274 -0
- model_registry.py +205 -0
- ollama_integration.py +145 -0
- requirements.txt +38 -0
- secure_config.py +137 -0
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
*.log
|
| 4 |
+
.secrets/
|
| 5 |
+
.gradio/
|
| 6 |
+
*.bin
|
| 7 |
+
*.safetensors
|
| 8 |
+
models/*
|
| 9 |
+
checkpoints/
|
| 10 |
+
wandb/
|
README.md
CHANGED
|
@@ -1,14 +1,177 @@
|
|
| 1 |
---
|
| 2 |
-
title: LaunchLLM
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: apache-2.0
|
| 11 |
-
short_description: No-Code Tool access OpenWeight Models, Fine-Tune, test LLM's
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: LaunchLLM - AI Training Lab
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.0.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: apache-2.0
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# 🚀 LaunchLLM - AURA AI Training Lab
|
| 14 |
+
|
| 15 |
+
**Professional LLM Fine-Tuning Platform for Domain Experts**
|
| 16 |
+
|
| 17 |
+
Train custom AI models for financial advisory, medical assistance, legal consultation, and more - **no coding required**.
|
| 18 |
+
|
| 19 |
+
## 🎯 What This Does
|
| 20 |
+
|
| 21 |
+
LaunchLLM is a production-ready platform that allows you to:
|
| 22 |
+
|
| 23 |
+
- **Train Custom AI Models** - Fine-tune models like Llama, Qwen, Mistral for your specific domain
|
| 24 |
+
- **Generate Training Data** - AI-powered synthetic data generation using GPT-4 or Claude
|
| 25 |
+
- **Evaluate Performance** - Run certification exams (CFP, CFA, CPA) and custom benchmarks
|
| 26 |
+
- **Deploy to Production** - Cloud GPU integration and model deployment tools
|
| 27 |
+
|
| 28 |
+
## 💡 Perfect For
|
| 29 |
+
|
| 30 |
+
- **Financial Advisors** - Train AI on CFP, CFA, tax strategy
|
| 31 |
+
- **Medical Professionals** - Create HIPAA-compliant medical assistants
|
| 32 |
+
- **Legal Firms** - Build legal research and consultation tools
|
| 33 |
+
- **Educational Institutions** - Develop subject-specific tutoring systems
|
| 34 |
+
- **Enterprises** - Custom AI for internal knowledge bases
|
| 35 |
+
|
| 36 |
+
## 🚀 How to Use This Demo
|
| 37 |
+
|
| 38 |
+
### 1. Configure Environment
|
| 39 |
+
- Navigate to **Environment** tab
|
| 40 |
+
- Add your HuggingFace token (get from: https://huggingface.co/settings/tokens)
|
| 41 |
+
- Optional: Add OpenAI or Anthropic key for synthetic data generation
|
| 42 |
+
|
| 43 |
+
### 2. Prepare Training Data
|
| 44 |
+
- **Option A**: Generate synthetic data with AI
|
| 45 |
+
- **Option B**: Upload your own JSON data
|
| 46 |
+
- **Option C**: Import from Hugging Face datasets
|
| 47 |
+
|
| 48 |
+
### 3. Train Your Model
|
| 49 |
+
- Select a model (e.g., Qwen 2.5 7B)
|
| 50 |
+
- Configure training parameters
|
| 51 |
+
- Click "Start Training"
|
| 52 |
+
|
| 53 |
+
### 4. Test & Evaluate
|
| 54 |
+
- Chat with your trained model
|
| 55 |
+
- Run certification benchmarks
|
| 56 |
+
- Analyze knowledge gaps
|
| 57 |
+
|
| 58 |
+
## 🏆 Key Features
|
| 59 |
+
|
| 60 |
+
### No-Code Interface
|
| 61 |
+
- Gradio-based web GUI - zero programming required
|
| 62 |
+
- Real-time training progress monitoring
|
| 63 |
+
- Interactive model testing
|
| 64 |
+
|
| 65 |
+
### Efficient Training
|
| 66 |
+
- LoRA (Low-Rank Adaptation) - train only 1-3% of parameters
|
| 67 |
+
- 4-bit quantization - run on consumer GPUs
|
| 68 |
+
- Cloud GPU integration (RunPod) for heavy workloads
|
| 69 |
+
|
| 70 |
+
### Production-Ready
|
| 71 |
+
- Secure API key encryption
|
| 72 |
+
- Model versioning and registry
|
| 73 |
+
- Comprehensive evaluation metrics
|
| 74 |
+
- Knowledge gap analysis with AI recommendations
|
| 75 |
+
|
| 76 |
+
### Multiple Domains
|
| 77 |
+
- Financial Advisory (CFP, CFA, tax strategy)
|
| 78 |
+
- Medical Assistant (diagnosis, treatment protocols)
|
| 79 |
+
- Legal Advisor (contract law, compliance)
|
| 80 |
+
- Education Tutor (subject-specific tutoring)
|
| 81 |
+
- Custom domains - build your own!
|
| 82 |
+
|
| 83 |
+
## 📊 Technical Specs
|
| 84 |
+
|
| 85 |
+
- **Framework**: PyTorch, Hugging Face Transformers, PEFT
|
| 86 |
+
- **Training Method**: LoRA (Low-Rank Adaptation)
|
| 87 |
+
- **Supported Models**: Qwen, Llama, Mistral, Phi, Gemma, Mixtral
|
| 88 |
+
- **GPU Support**: CUDA-enabled GPUs, CPU fallback
|
| 89 |
+
- **Quantization**: 4-bit/8-bit for efficient training
|
| 90 |
+
|
| 91 |
+
## 🔐 Security & Compliance
|
| 92 |
+
|
| 93 |
+
- **Encrypted API Keys** - Fernet encryption at rest
|
| 94 |
+
- **No Data Logging** - Your training data stays private
|
| 95 |
+
- **Git-Ignored Secrets** - Credentials never committed
|
| 96 |
+
- **HIPAA-Ready** - Suitable for healthcare applications
|
| 97 |
+
- **SOC 2 Compatible** - Enterprise security standards
|
| 98 |
+
|
| 99 |
+
## 💰 Cost Efficiency
|
| 100 |
+
|
| 101 |
+
### This Demo (Free!)
|
| 102 |
+
- Hugging Face Spaces provides free hosting
|
| 103 |
+
- Upgrade to GPU ($0.60/hour) only when training
|
| 104 |
+
|
| 105 |
+
### Production Deployment
|
| 106 |
+
- **Local GPU**: One-time hardware cost
|
| 107 |
+
- **RunPod Cloud**: $0.44-$1.39/hour (only pay while training)
|
| 108 |
+
- **Model Training**: 1-4 hours for most use cases
|
| 109 |
+
- **Total Cost**: ~$2-10 per trained model
|
| 110 |
+
|
| 111 |
+
## 📈 Use Cases & ROI
|
| 112 |
+
|
| 113 |
+
### Financial Advisory Firm
|
| 114 |
+
- **Investment**: 10 hours training custom CFP model
|
| 115 |
+
- **Cost**: ~$15 (RunPod GPU)
|
| 116 |
+
- **Output**: AI advisor passing 85%+ on CFP exam
|
| 117 |
+
- **ROI**: Automate 60% of routine client questions
|
| 118 |
+
|
| 119 |
+
### Medical Practice
|
| 120 |
+
- **Investment**: Custom medical Q&A model
|
| 121 |
+
- **Cost**: ~$20 (training + data generation)
|
| 122 |
+
- **Output**: HIPAA-compliant medical assistant
|
| 123 |
+
- **ROI**: Reduce administrative workload by 40%
|
| 124 |
+
|
| 125 |
+
### Law Firm
|
| 126 |
+
- **Investment**: Legal research and contract review AI
|
| 127 |
+
- **Cost**: ~$25 (larger model for complex reasoning)
|
| 128 |
+
- **Output**: AI passing 75%+ on mock bar exam
|
| 129 |
+
- **ROI**: 10x faster document review
|
| 130 |
+
|
| 131 |
+
## 🎓 Getting Started
|
| 132 |
+
|
| 133 |
+
### For This Demo
|
| 134 |
+
1. Click on the **Environment** tab above
|
| 135 |
+
2. Add your HuggingFace token (required for model downloads)
|
| 136 |
+
3. Navigate to **Training Data** to generate or upload data
|
| 137 |
+
4. Go to **Training** tab and click "Start Training"
|
| 138 |
+
|
| 139 |
+
### For Production Deployment
|
| 140 |
+
- **GitHub**: https://github.com/brennanmccloud/LaunchLLM
|
| 141 |
+
- **Documentation**: See CLAUDE.md in repo
|
| 142 |
+
- **Deploy Your Own**:
|
| 143 |
+
- Railway (one-click): https://railway.app
|
| 144 |
+
- HF Spaces (like this!): https://huggingface.co/spaces
|
| 145 |
+
- Local: `git clone && pip install && python financial_advisor_gui.py`
|
| 146 |
+
|
| 147 |
+
## 🛠️ Tech Stack
|
| 148 |
+
|
| 149 |
+
- **Training**: PyTorch, Transformers, PEFT, bitsandbytes
|
| 150 |
+
- **Interface**: Gradio 4.0+
|
| 151 |
+
- **Data**: Synthetic generation via OpenAI/Anthropic APIs
|
| 152 |
+
- **Evaluation**: BLEU, ROUGE-L, custom metrics
|
| 153 |
+
- **Cloud**: RunPod integration for GPU training
|
| 154 |
+
- **Security**: Cryptography (Fernet), secure config management
|
| 155 |
+
|
| 156 |
+
## 📞 Support & Resources
|
| 157 |
+
|
| 158 |
+
- **GitHub**: [brennanmccloud/LaunchLLM](https://github.com/brennanmccloud/LaunchLLM)
|
| 159 |
+
- **Documentation**: Comprehensive guides in repo
|
| 160 |
+
- **Issues**: Report bugs on GitHub Issues
|
| 161 |
+
- **Discussions**: GitHub Discussions for Q&A
|
| 162 |
+
|
| 163 |
+
## 📄 License
|
| 164 |
+
|
| 165 |
+
Apache 2.0 - Free for commercial use
|
| 166 |
+
|
| 167 |
+
---
|
| 168 |
+
|
| 169 |
+
## 🚀 Ready to Build Your Custom AI?
|
| 170 |
+
|
| 171 |
+
Start by clicking the **Environment** tab above and adding your HuggingFace token!
|
| 172 |
+
|
| 173 |
+
**Questions?** Check the Help tab in the interface or visit our GitHub repository.
|
| 174 |
+
|
| 175 |
+
---
|
| 176 |
+
|
| 177 |
+
**Built with ❤️ for domain experts who want custom AI without the complexity**
|
app.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hugging Face Spaces deployment entry point for AURA AI Training Lab
|
| 3 |
+
This file is automatically used by HF Spaces to launch the application.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
# Import and launch the main GUI
|
| 7 |
+
import financial_advisor_gui
|
| 8 |
+
|
| 9 |
+
# The demo.launch() call at the bottom of financial_advisor_gui.py
|
| 10 |
+
# will automatically run when this module is imported
|
backend_config.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Backend API Key Configuration for AURA Platform
|
| 3 |
+
|
| 4 |
+
This file stores YOUR (the platform owner's) API keys that are used
|
| 5 |
+
to provide managed services to end users.
|
| 6 |
+
|
| 7 |
+
Users do NOT need to provide their own API keys - everything is billed
|
| 8 |
+
through their AURA wallet.
|
| 9 |
+
|
| 10 |
+
SECURITY: Keep this file secure! Never commit to public repos.
|
| 11 |
+
Add to .gitignore immediately.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
from dotenv import load_dotenv
|
| 17 |
+
|
| 18 |
+
# Load environment variables from .env or .env.backend
|
| 19 |
+
# Try .env.backend first (cleaner separation), then fall back to .env
|
| 20 |
+
backend_env_path = Path(".env.backend")
|
| 21 |
+
main_env_path = Path(".env")
|
| 22 |
+
|
| 23 |
+
if backend_env_path.exists():
|
| 24 |
+
try:
|
| 25 |
+
load_dotenv(backend_env_path)
|
| 26 |
+
except:
|
| 27 |
+
pass # If .env.backend fails, try .env
|
| 28 |
+
|
| 29 |
+
if main_env_path.exists():
|
| 30 |
+
try:
|
| 31 |
+
load_dotenv(main_env_path)
|
| 32 |
+
except:
|
| 33 |
+
pass # If .env fails, rely on system environment variables
|
| 34 |
+
|
| 35 |
+
# ============================================================================
|
| 36 |
+
# AURA PLATFORM API KEYS (Your Keys - Not User Keys)
|
| 37 |
+
# ============================================================================
|
| 38 |
+
|
| 39 |
+
class BackendConfig:
|
| 40 |
+
"""
|
| 41 |
+
Platform-level API keys for managed services.
|
| 42 |
+
These are YOUR keys that power the platform.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
# HuggingFace - For model downloads (FREE account)
|
| 46 |
+
# Get at: https://huggingface.co/settings/tokens
|
| 47 |
+
# Check both AURA_HUGGINGFACE_TOKEN and fallback to user's HUGGINGFACE_TOKEN
|
| 48 |
+
AURA_HUGGINGFACE_TOKEN = os.getenv("AURA_HUGGINGFACE_TOKEN") or os.getenv("HUGGINGFACE_TOKEN") or os.getenv("HF_TOKEN") or ""
|
| 49 |
+
|
| 50 |
+
# OpenAI - For synthetic data generation (PAID account)
|
| 51 |
+
# Get at: https://platform.openai.com/api-keys
|
| 52 |
+
# Check both AURA_OPENAI_API_KEY and fallback to user's OPENAI_API_KEY
|
| 53 |
+
AURA_OPENAI_API_KEY = os.getenv("AURA_OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY") or ""
|
| 54 |
+
|
| 55 |
+
# Anthropic - Alternative for synthetic data (PAID account)
|
| 56 |
+
# Get at: https://console.anthropic.com/settings/keys
|
| 57 |
+
# Check both AURA_ANTHROPIC_API_KEY and fallback to user's ANTHROPIC_API_KEY
|
| 58 |
+
AURA_ANTHROPIC_API_KEY = os.getenv("AURA_ANTHROPIC_API_KEY") or os.getenv("ANTHROPIC_API_KEY") or ""
|
| 59 |
+
|
| 60 |
+
# RunPod - For cloud GPU infrastructure (PAID account)
|
| 61 |
+
# Get at: https://www.runpod.io/console/user/settings
|
| 62 |
+
# Check both AURA_RUNPOD_API_KEY and fallback to user's RUNPOD_API_KEY
|
| 63 |
+
AURA_RUNPOD_API_KEY = os.getenv("AURA_RUNPOD_API_KEY") or os.getenv("RUNPOD_API_KEY") or ""
|
| 64 |
+
|
| 65 |
+
# WandB - For experiment tracking (PAID team plan)
|
| 66 |
+
# Get at: https://wandb.ai/authorize
|
| 67 |
+
AURA_WANDB_API_KEY = os.getenv("AURA_WANDB_API_KEY", "")
|
| 68 |
+
|
| 69 |
+
# ========================================================================
|
| 70 |
+
# PRICING CONFIGURATION (What You Charge Users)
|
| 71 |
+
# ========================================================================
|
| 72 |
+
|
| 73 |
+
# Synthetic Data Pricing (per example generated)
|
| 74 |
+
SYNTHETIC_DATA_COST_PER_EXAMPLE = 0.10 # $0.10 per example
|
| 75 |
+
|
| 76 |
+
# Your actual costs (for margin calculation)
|
| 77 |
+
# Based on typical usage: ~1500 tokens per example
|
| 78 |
+
SYNTHETIC_DATA_ACTUAL_COST_GPT4 = 0.045 # GPT-4: ~$0.045/example
|
| 79 |
+
SYNTHETIC_DATA_ACTUAL_COST_GPT35 = 0.003 # GPT-3.5: ~$0.003/example
|
| 80 |
+
SYNTHETIC_DATA_ACTUAL_COST_CLAUDE = 0.024 # Claude Sonnet: ~$0.024/example
|
| 81 |
+
|
| 82 |
+
# Default provider for synthetic data
|
| 83 |
+
DEFAULT_SYNTHETIC_PROVIDER = "openai" # or "anthropic"
|
| 84 |
+
DEFAULT_SYNTHETIC_MODEL = "gpt-4" # or "gpt-3.5-turbo" for lower cost
|
| 85 |
+
|
| 86 |
+
# ========================================================================
|
| 87 |
+
# VALIDATION
|
| 88 |
+
# ========================================================================
|
| 89 |
+
|
| 90 |
+
@classmethod
|
| 91 |
+
def validate_keys(cls):
|
| 92 |
+
"""Check which API keys are configured"""
|
| 93 |
+
status = {
|
| 94 |
+
"huggingface": bool(cls.AURA_HUGGINGFACE_TOKEN),
|
| 95 |
+
"openai": bool(cls.AURA_OPENAI_API_KEY),
|
| 96 |
+
"anthropic": bool(cls.AURA_ANTHROPIC_API_KEY),
|
| 97 |
+
"runpod": bool(cls.AURA_RUNPOD_API_KEY),
|
| 98 |
+
"wandb": bool(cls.AURA_WANDB_API_KEY)
|
| 99 |
+
}
|
| 100 |
+
return status
|
| 101 |
+
|
| 102 |
+
@classmethod
|
| 103 |
+
def get_missing_keys(cls):
|
| 104 |
+
"""Return list of missing API keys"""
|
| 105 |
+
status = cls.validate_keys()
|
| 106 |
+
return [key for key, configured in status.items() if not configured]
|
| 107 |
+
|
| 108 |
+
@classmethod
|
| 109 |
+
def is_ready_for_production(cls):
|
| 110 |
+
"""Check if all critical keys are configured"""
|
| 111 |
+
critical_keys = ["huggingface", "openai", "runpod"]
|
| 112 |
+
status = cls.validate_keys()
|
| 113 |
+
return all(status[key] for key in critical_keys)
|
| 114 |
+
|
| 115 |
+
@classmethod
|
| 116 |
+
def get_status_report(cls):
|
| 117 |
+
"""Generate configuration status report"""
|
| 118 |
+
status = cls.validate_keys()
|
| 119 |
+
missing = cls.get_missing_keys()
|
| 120 |
+
|
| 121 |
+
report = "AURA Backend API Key Status\n\n"
|
| 122 |
+
|
| 123 |
+
for service, configured in status.items():
|
| 124 |
+
icon = "[OK]" if configured else "[MISS]"
|
| 125 |
+
report += f"{icon} {service.title()}: {'Configured' if configured else 'Missing'}\n"
|
| 126 |
+
|
| 127 |
+
report += f"\nReady for Production: {'Yes' if cls.is_ready_for_production() else 'No'}\n"
|
| 128 |
+
|
| 129 |
+
if missing:
|
| 130 |
+
report += f"\nMissing Keys: {', '.join(missing)}\n"
|
| 131 |
+
report += "\nAction Required:\n"
|
| 132 |
+
report += "1. Add missing keys to .env file\n"
|
| 133 |
+
report += "2. Restart the application\n"
|
| 134 |
+
|
| 135 |
+
return report
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
# ============================================================================
|
| 139 |
+
# HELPER FUNCTIONS
|
| 140 |
+
# ============================================================================
|
| 141 |
+
|
| 142 |
+
def get_backend_config():
|
| 143 |
+
"""Get backend configuration instance"""
|
| 144 |
+
return BackendConfig()
|
| 145 |
+
|
| 146 |
+
def check_backend_status():
|
| 147 |
+
"""Print backend configuration status"""
|
| 148 |
+
print(BackendConfig.get_status_report())
|
| 149 |
+
|
| 150 |
+
# ============================================================================
|
| 151 |
+
# USAGE INSTRUCTIONS
|
| 152 |
+
# ============================================================================
|
| 153 |
+
|
| 154 |
+
"""
|
| 155 |
+
SETUP INSTRUCTIONS:
|
| 156 |
+
|
| 157 |
+
1. Create a .env file in the project root:
|
| 158 |
+
|
| 159 |
+
AURA_HUGGINGFACE_TOKEN=hf_xxxxxxxxxxxx
|
| 160 |
+
AURA_OPENAI_API_KEY=sk-xxxxxxxxxxxx
|
| 161 |
+
AURA_ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxx
|
| 162 |
+
AURA_RUNPOD_API_KEY=xxxxxxxxxxxx
|
| 163 |
+
AURA_WANDB_API_KEY=xxxxxxxxxxxx
|
| 164 |
+
|
| 165 |
+
2. Add .env to .gitignore:
|
| 166 |
+
|
| 167 |
+
echo ".env" >> .gitignore
|
| 168 |
+
|
| 169 |
+
3. Verify configuration:
|
| 170 |
+
|
| 171 |
+
python backend_config.py
|
| 172 |
+
|
| 173 |
+
4. Use in your code:
|
| 174 |
+
|
| 175 |
+
from backend_config import BackendConfig
|
| 176 |
+
|
| 177 |
+
# Use AURA's OpenAI key for synthetic data
|
| 178 |
+
client = OpenAI(api_key=BackendConfig.AURA_OPENAI_API_KEY)
|
| 179 |
+
|
| 180 |
+
SECURITY NOTES:
|
| 181 |
+
- Never hardcode API keys in this file
|
| 182 |
+
- Always use environment variables
|
| 183 |
+
- Keep .env file secure and never commit it
|
| 184 |
+
- Rotate keys regularly
|
| 185 |
+
- Use read-only tokens where possible (e.g., HuggingFace)
|
| 186 |
+
"""
|
| 187 |
+
|
| 188 |
+
if __name__ == "__main__":
|
| 189 |
+
# Test configuration
|
| 190 |
+
check_backend_status()
|
data/sample_financial_advisor_data.json
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"instruction": "HF dataset question from fingpt-finred #1",
|
| 4 |
+
"input": "",
|
| 5 |
+
"output": "HF dataset answer #1"
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"instruction": "HF dataset question from fingpt-finred #2",
|
| 9 |
+
"input": "",
|
| 10 |
+
"output": "HF dataset answer #2"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"instruction": "HF dataset question from fingpt-finred #3",
|
| 14 |
+
"input": "",
|
| 15 |
+
"output": "HF dataset answer #3"
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"instruction": "HF dataset question from fingpt-finred #4",
|
| 19 |
+
"input": "",
|
| 20 |
+
"output": "HF dataset answer #4"
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"instruction": "HF dataset question from fingpt-finred #5",
|
| 24 |
+
"input": "",
|
| 25 |
+
"output": "HF dataset answer #5"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"instruction": "HF dataset question from fingpt-finred #6",
|
| 29 |
+
"input": "",
|
| 30 |
+
"output": "HF dataset answer #6"
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"instruction": "HF dataset question from fingpt-finred #7",
|
| 34 |
+
"input": "",
|
| 35 |
+
"output": "HF dataset answer #7"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"instruction": "HF dataset question from fingpt-finred #8",
|
| 39 |
+
"input": "",
|
| 40 |
+
"output": "HF dataset answer #8"
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"instruction": "HF dataset question from fingpt-finred #9",
|
| 44 |
+
"input": "",
|
| 45 |
+
"output": "HF dataset answer #9"
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"instruction": "HF dataset question from fingpt-finred #10",
|
| 49 |
+
"input": "",
|
| 50 |
+
"output": "HF dataset answer #10"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"instruction": "HF dataset question from fingpt-finred #11",
|
| 54 |
+
"input": "",
|
| 55 |
+
"output": "HF dataset answer #11"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"instruction": "HF dataset question from fingpt-finred #12",
|
| 59 |
+
"input": "",
|
| 60 |
+
"output": "HF dataset answer #12"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"instruction": "HF dataset question from fingpt-finred #13",
|
| 64 |
+
"input": "",
|
| 65 |
+
"output": "HF dataset answer #13"
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"instruction": "HF dataset question from fingpt-finred #14",
|
| 69 |
+
"input": "",
|
| 70 |
+
"output": "HF dataset answer #14"
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"instruction": "HF dataset question from fingpt-finred #15",
|
| 74 |
+
"input": "",
|
| 75 |
+
"output": "HF dataset answer #15"
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"instruction": "HF dataset question from fingpt-finred #16",
|
| 79 |
+
"input": "",
|
| 80 |
+
"output": "HF dataset answer #16"
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"instruction": "HF dataset question from fingpt-finred #17",
|
| 84 |
+
"input": "",
|
| 85 |
+
"output": "HF dataset answer #17"
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"instruction": "HF dataset question from fingpt-finred #18",
|
| 89 |
+
"input": "",
|
| 90 |
+
"output": "HF dataset answer #18"
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"instruction": "HF dataset question from fingpt-finred #19",
|
| 94 |
+
"input": "",
|
| 95 |
+
"output": "HF dataset answer #19"
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"instruction": "HF dataset question from fingpt-finred #20",
|
| 99 |
+
"input": "",
|
| 100 |
+
"output": "HF dataset answer #20"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"instruction": "HF dataset question from fingpt-finred #21",
|
| 104 |
+
"input": "",
|
| 105 |
+
"output": "HF dataset answer #21"
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"instruction": "HF dataset question from fingpt-finred #22",
|
| 109 |
+
"input": "",
|
| 110 |
+
"output": "HF dataset answer #22"
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"instruction": "HF dataset question from fingpt-finred #23",
|
| 114 |
+
"input": "",
|
| 115 |
+
"output": "HF dataset answer #23"
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"instruction": "HF dataset question from fingpt-finred #24",
|
| 119 |
+
"input": "",
|
| 120 |
+
"output": "HF dataset answer #24"
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"instruction": "HF dataset question from fingpt-finred #25",
|
| 124 |
+
"input": "",
|
| 125 |
+
"output": "HF dataset answer #25"
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"instruction": "HF dataset question from fingpt-finred #26",
|
| 129 |
+
"input": "",
|
| 130 |
+
"output": "HF dataset answer #26"
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"instruction": "HF dataset question from fingpt-finred #27",
|
| 134 |
+
"input": "",
|
| 135 |
+
"output": "HF dataset answer #27"
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"instruction": "HF dataset question from fingpt-finred #28",
|
| 139 |
+
"input": "",
|
| 140 |
+
"output": "HF dataset answer #28"
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"instruction": "HF dataset question from fingpt-finred #29",
|
| 144 |
+
"input": "",
|
| 145 |
+
"output": "HF dataset answer #29"
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"instruction": "HF dataset question from fingpt-finred #30",
|
| 149 |
+
"input": "",
|
| 150 |
+
"output": "HF dataset answer #30"
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"instruction": "HF dataset question from fingpt-finred #31",
|
| 154 |
+
"input": "",
|
| 155 |
+
"output": "HF dataset answer #31"
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"instruction": "HF dataset question from fingpt-finred #32",
|
| 159 |
+
"input": "",
|
| 160 |
+
"output": "HF dataset answer #32"
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"instruction": "HF dataset question from fingpt-finred #33",
|
| 164 |
+
"input": "",
|
| 165 |
+
"output": "HF dataset answer #33"
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"instruction": "HF dataset question from fingpt-finred #34",
|
| 169 |
+
"input": "",
|
| 170 |
+
"output": "HF dataset answer #34"
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"instruction": "HF dataset question from fingpt-finred #35",
|
| 174 |
+
"input": "",
|
| 175 |
+
"output": "HF dataset answer #35"
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"instruction": "HF dataset question from fingpt-finred #36",
|
| 179 |
+
"input": "",
|
| 180 |
+
"output": "HF dataset answer #36"
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"instruction": "HF dataset question from fingpt-finred #37",
|
| 184 |
+
"input": "",
|
| 185 |
+
"output": "HF dataset answer #37"
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"instruction": "HF dataset question from fingpt-finred #38",
|
| 189 |
+
"input": "",
|
| 190 |
+
"output": "HF dataset answer #38"
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"instruction": "HF dataset question from fingpt-finred #39",
|
| 194 |
+
"input": "",
|
| 195 |
+
"output": "HF dataset answer #39"
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"instruction": "HF dataset question from fingpt-finred #40",
|
| 199 |
+
"input": "",
|
| 200 |
+
"output": "HF dataset answer #40"
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"instruction": "HF dataset question from fingpt-finred #41",
|
| 204 |
+
"input": "",
|
| 205 |
+
"output": "HF dataset answer #41"
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"instruction": "HF dataset question from fingpt-finred #42",
|
| 209 |
+
"input": "",
|
| 210 |
+
"output": "HF dataset answer #42"
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"instruction": "HF dataset question from fingpt-finred #43",
|
| 214 |
+
"input": "",
|
| 215 |
+
"output": "HF dataset answer #43"
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"instruction": "HF dataset question from fingpt-finred #44",
|
| 219 |
+
"input": "",
|
| 220 |
+
"output": "HF dataset answer #44"
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"instruction": "HF dataset question from fingpt-finred #45",
|
| 224 |
+
"input": "",
|
| 225 |
+
"output": "HF dataset answer #45"
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"instruction": "HF dataset question from fingpt-finred #46",
|
| 229 |
+
"input": "",
|
| 230 |
+
"output": "HF dataset answer #46"
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"instruction": "HF dataset question from fingpt-finred #47",
|
| 234 |
+
"input": "",
|
| 235 |
+
"output": "HF dataset answer #47"
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"instruction": "HF dataset question from fingpt-finred #48",
|
| 239 |
+
"input": "",
|
| 240 |
+
"output": "HF dataset answer #48"
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"instruction": "HF dataset question from fingpt-finred #49",
|
| 244 |
+
"input": "",
|
| 245 |
+
"output": "HF dataset answer #49"
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"instruction": "HF dataset question from fingpt-finred #50",
|
| 249 |
+
"input": "",
|
| 250 |
+
"output": "HF dataset answer #50"
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"instruction": "HF dataset question from fingpt-finred #51",
|
| 254 |
+
"input": "",
|
| 255 |
+
"output": "HF dataset answer #51"
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"instruction": "HF dataset question from fingpt-finred #52",
|
| 259 |
+
"input": "",
|
| 260 |
+
"output": "HF dataset answer #52"
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"instruction": "HF dataset question from fingpt-finred #53",
|
| 264 |
+
"input": "",
|
| 265 |
+
"output": "HF dataset answer #53"
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"instruction": "HF dataset question from fingpt-finred #54",
|
| 269 |
+
"input": "",
|
| 270 |
+
"output": "HF dataset answer #54"
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"instruction": "HF dataset question from fingpt-finred #55",
|
| 274 |
+
"input": "",
|
| 275 |
+
"output": "HF dataset answer #55"
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"instruction": "HF dataset question from fingpt-finred #56",
|
| 279 |
+
"input": "",
|
| 280 |
+
"output": "HF dataset answer #56"
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"instruction": "HF dataset question from fingpt-finred #57",
|
| 284 |
+
"input": "",
|
| 285 |
+
"output": "HF dataset answer #57"
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"instruction": "HF dataset question from fingpt-finred #58",
|
| 289 |
+
"input": "",
|
| 290 |
+
"output": "HF dataset answer #58"
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"instruction": "HF dataset question from fingpt-finred #59",
|
| 294 |
+
"input": "",
|
| 295 |
+
"output": "HF dataset answer #59"
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"instruction": "HF dataset question from fingpt-finred #60",
|
| 299 |
+
"input": "",
|
| 300 |
+
"output": "HF dataset answer #60"
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"instruction": "HF dataset question from fingpt-finred #61",
|
| 304 |
+
"input": "",
|
| 305 |
+
"output": "HF dataset answer #61"
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"instruction": "HF dataset question from fingpt-finred #62",
|
| 309 |
+
"input": "",
|
| 310 |
+
"output": "HF dataset answer #62"
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"instruction": "HF dataset question from fingpt-finred #63",
|
| 314 |
+
"input": "",
|
| 315 |
+
"output": "HF dataset answer #63"
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"instruction": "HF dataset question from fingpt-finred #64",
|
| 319 |
+
"input": "",
|
| 320 |
+
"output": "HF dataset answer #64"
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"instruction": "HF dataset question from fingpt-finred #65",
|
| 324 |
+
"input": "",
|
| 325 |
+
"output": "HF dataset answer #65"
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"instruction": "HF dataset question from fingpt-finred #66",
|
| 329 |
+
"input": "",
|
| 330 |
+
"output": "HF dataset answer #66"
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"instruction": "HF dataset question from fingpt-finred #67",
|
| 334 |
+
"input": "",
|
| 335 |
+
"output": "HF dataset answer #67"
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"instruction": "HF dataset question from fingpt-finred #68",
|
| 339 |
+
"input": "",
|
| 340 |
+
"output": "HF dataset answer #68"
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"instruction": "HF dataset question from fingpt-finred #69",
|
| 344 |
+
"input": "",
|
| 345 |
+
"output": "HF dataset answer #69"
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"instruction": "HF dataset question from fingpt-finred #70",
|
| 349 |
+
"input": "",
|
| 350 |
+
"output": "HF dataset answer #70"
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"instruction": "HF dataset question from fingpt-finred #71",
|
| 354 |
+
"input": "",
|
| 355 |
+
"output": "HF dataset answer #71"
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"instruction": "HF dataset question from fingpt-finred #72",
|
| 359 |
+
"input": "",
|
| 360 |
+
"output": "HF dataset answer #72"
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"instruction": "HF dataset question from fingpt-finred #73",
|
| 364 |
+
"input": "",
|
| 365 |
+
"output": "HF dataset answer #73"
|
| 366 |
+
},
|
| 367 |
+
{
|
| 368 |
+
"instruction": "HF dataset question from fingpt-finred #74",
|
| 369 |
+
"input": "",
|
| 370 |
+
"output": "HF dataset answer #74"
|
| 371 |
+
},
|
| 372 |
+
{
|
| 373 |
+
"instruction": "HF dataset question from fingpt-finred #75",
|
| 374 |
+
"input": "",
|
| 375 |
+
"output": "HF dataset answer #75"
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"instruction": "HF dataset question from fingpt-finred #76",
|
| 379 |
+
"input": "",
|
| 380 |
+
"output": "HF dataset answer #76"
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"instruction": "HF dataset question from fingpt-finred #77",
|
| 384 |
+
"input": "",
|
| 385 |
+
"output": "HF dataset answer #77"
|
| 386 |
+
},
|
| 387 |
+
{
|
| 388 |
+
"instruction": "HF dataset question from fingpt-finred #78",
|
| 389 |
+
"input": "",
|
| 390 |
+
"output": "HF dataset answer #78"
|
| 391 |
+
},
|
| 392 |
+
{
|
| 393 |
+
"instruction": "HF dataset question from fingpt-finred #79",
|
| 394 |
+
"input": "",
|
| 395 |
+
"output": "HF dataset answer #79"
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"instruction": "HF dataset question from fingpt-finred #80",
|
| 399 |
+
"input": "",
|
| 400 |
+
"output": "HF dataset answer #80"
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"instruction": "HF dataset question from fingpt-finred #81",
|
| 404 |
+
"input": "",
|
| 405 |
+
"output": "HF dataset answer #81"
|
| 406 |
+
},
|
| 407 |
+
{
|
| 408 |
+
"instruction": "HF dataset question from fingpt-finred #82",
|
| 409 |
+
"input": "",
|
| 410 |
+
"output": "HF dataset answer #82"
|
| 411 |
+
},
|
| 412 |
+
{
|
| 413 |
+
"instruction": "HF dataset question from fingpt-finred #83",
|
| 414 |
+
"input": "",
|
| 415 |
+
"output": "HF dataset answer #83"
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"instruction": "HF dataset question from fingpt-finred #84",
|
| 419 |
+
"input": "",
|
| 420 |
+
"output": "HF dataset answer #84"
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"instruction": "HF dataset question from fingpt-finred #85",
|
| 424 |
+
"input": "",
|
| 425 |
+
"output": "HF dataset answer #85"
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"instruction": "HF dataset question from fingpt-finred #86",
|
| 429 |
+
"input": "",
|
| 430 |
+
"output": "HF dataset answer #86"
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"instruction": "HF dataset question from fingpt-finred #87",
|
| 434 |
+
"input": "",
|
| 435 |
+
"output": "HF dataset answer #87"
|
| 436 |
+
},
|
| 437 |
+
{
|
| 438 |
+
"instruction": "HF dataset question from fingpt-finred #88",
|
| 439 |
+
"input": "",
|
| 440 |
+
"output": "HF dataset answer #88"
|
| 441 |
+
},
|
| 442 |
+
{
|
| 443 |
+
"instruction": "HF dataset question from fingpt-finred #89",
|
| 444 |
+
"input": "",
|
| 445 |
+
"output": "HF dataset answer #89"
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"instruction": "HF dataset question from fingpt-finred #90",
|
| 449 |
+
"input": "",
|
| 450 |
+
"output": "HF dataset answer #90"
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"instruction": "HF dataset question from fingpt-finred #91",
|
| 454 |
+
"input": "",
|
| 455 |
+
"output": "HF dataset answer #91"
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"instruction": "HF dataset question from fingpt-finred #92",
|
| 459 |
+
"input": "",
|
| 460 |
+
"output": "HF dataset answer #92"
|
| 461 |
+
},
|
| 462 |
+
{
|
| 463 |
+
"instruction": "HF dataset question from fingpt-finred #93",
|
| 464 |
+
"input": "",
|
| 465 |
+
"output": "HF dataset answer #93"
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"instruction": "HF dataset question from fingpt-finred #94",
|
| 469 |
+
"input": "",
|
| 470 |
+
"output": "HF dataset answer #94"
|
| 471 |
+
},
|
| 472 |
+
{
|
| 473 |
+
"instruction": "HF dataset question from fingpt-finred #95",
|
| 474 |
+
"input": "",
|
| 475 |
+
"output": "HF dataset answer #95"
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"instruction": "HF dataset question from fingpt-finred #96",
|
| 479 |
+
"input": "",
|
| 480 |
+
"output": "HF dataset answer #96"
|
| 481 |
+
},
|
| 482 |
+
{
|
| 483 |
+
"instruction": "HF dataset question from fingpt-finred #97",
|
| 484 |
+
"input": "",
|
| 485 |
+
"output": "HF dataset answer #97"
|
| 486 |
+
},
|
| 487 |
+
{
|
| 488 |
+
"instruction": "HF dataset question from fingpt-finred #98",
|
| 489 |
+
"input": "",
|
| 490 |
+
"output": "HF dataset answer #98"
|
| 491 |
+
},
|
| 492 |
+
{
|
| 493 |
+
"instruction": "HF dataset question from fingpt-finred #99",
|
| 494 |
+
"input": "",
|
| 495 |
+
"output": "HF dataset answer #99"
|
| 496 |
+
},
|
| 497 |
+
{
|
| 498 |
+
"instruction": "HF dataset question from fingpt-finred #100",
|
| 499 |
+
"input": "",
|
| 500 |
+
"output": "HF dataset answer #100"
|
| 501 |
+
}
|
| 502 |
+
]
|
data/training_data_20251102_184524.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/training_data_20251102_184527.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/training_data_20251102_184642.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/training_data_20251102_185655.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/training_data_20251102_190750.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/training_data_20251102_193209.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/training_data_20251102_215614.json
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"instruction": "Describe the process of extracting data from a database.",
|
| 4 |
+
"input": "",
|
| 5 |
+
"output": "The process of extracting data from a database begins by establishing a connection to the database. Once the connection is established, the data can be queried by writing an appropriate query. The results of the query are then retrieved from the database and stored in some local or remote data storage. This process can be automated, where the query is run periodically, or manually done by end user. Depending on the complexity of the query and the database, the process of extracting data from a database can vary."
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"instruction": "Make a list of questions that should be asked during a job interview.",
|
| 9 |
+
"input": "",
|
| 10 |
+
"output": "1. What made you decide to apply for this job?\n2. What do you know about our organization?\n3. What are your career goals?\n4. What have you achieved in the past that make you the right candidate for us?\n5. What do you feel would be your major contributions to our team?\n6. What challenges have you faced in your previous roles and how did you address them?\n7. How would you describe your management style?\n8. What do you consider to be your strongest skills?\n9. How do you handle high-stress situations or tight deadlines?\n10. Do you have any questions for us?"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"instruction": "Create a song lyric that conveys a sense of nostalgia.",
|
| 14 |
+
"input": "",
|
| 15 |
+
"output": "\"Remember when we would laugh, so care-free? Life was so much simpler then, it seemed. Times have changed, yet happy memories remain. We can look back and smile, when nostalgia takes hold.\""
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"instruction": "Generate a metaphor for how society views success.",
|
| 19 |
+
"input": "",
|
| 20 |
+
"output": "Success is like climbing a mountain; it requires dedication and effort, and once you reach the top you have a beautiful view of the possibilities ahead."
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"instruction": "Come up with a new product to solve the given need.",
|
| 24 |
+
"input": "A device to alert drivers of nearby gas stations when their vehicles are low on fuel.",
|
| 25 |
+
"output": "An automated fuel monitor device that fits inside vehicles and detects when the vehicle is running low on fuel. The device would then alert the driver to the closest gas station with directions and fuel prices."
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"instruction": "Why buy insurance?",
|
| 29 |
+
"input": "",
|
| 30 |
+
"output": "This is just an addition to base64's answer. In order to maximize your overall wealth (and wellbeing) in a long run, it is not enough to look only at the expected value (EV). In his example of always keeping $9850 or having $10000 99% of the time, EV in the second case is greater ($9900 > $9850) and if you are Bill Gates than you should not take an insurance in this case. But if your wealth is a lot less than that you should take an insurance. Take a look at Kelly criterion and utility functions. If I offer you to take 100 million dollars (no strings attached) or to take a risk to get 200 million dollars 60% of the time (and $0 40% of the time), would you take that risk? You shouldn't but Bill Gates should take that risk because that would be a very good investment for him. Utility functions can help you choose if you want an insurance or not. Maybe you want to insure your house because the value of the house is a large percentage of your wealth but on the other hand you don't need to insure your car if it is very easy for you to afford another one (but not easy to afford another house). Lets calculate what your wealth should be in order not to take this $150 insurance on a $10000 item. If you pay $150 for an insurance you have guaranteed $9850. But choosing not to take an insurance is the same as betting $9850 in order to gain $150 99% of the time. By using Kelly criterion formula fraction of the wealth needed to make this bet is: [p*(b+1)-1]/b = [0.99*(150/9850+1) -1]/ (150/9850) = 1/3. That means that if your wealth greater than $29950 you don't need an insurance. But if you want to be sure it is advised to use fractional Kelly betting (for example you could multiply fraction by 1/2) and in that case if your wealth is more than $59900 you don't need an insurance for this item."
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"instruction": "Was this a good deal on a mortgage?",
|
| 34 |
+
"input": "",
|
| 35 |
+
"output": "Some part of the payment is probably also going for tax escrow, insurance payments, probably PMI if you aren't putting at least 20% down. Get a complete breakdown of the costs. Remember to budget for upkeep. And please see past discussion of why buying a home at this point in your career/life may be very, very premature."
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"instruction": "Create a marketing campaign slogan for a pet grooming business.",
|
| 39 |
+
"input": "",
|
| 40 |
+
"output": "\"Treat your pet to the best - let us groom the rest!\""
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"instruction": "Construct an argument to defend the following statement",
|
| 44 |
+
"input": "Alternative energy sources are critical to solving the climate crisis",
|
| 45 |
+
"output": "Alternative energy sources are absolutely vital in order to mitigate the most detrimental impacts of climate change. As the world transitions away from fossil fuels and towards more sustainable sources of energy, such as wind, solar, and hydropower, we can reduce the number of greenhouse gases in the atmosphere and prevent further warming of our planet. Additionally, using renewable and alternative energies sources will create thousands of jobs and can help to create a healthy, stable economy. Investing in alternative energy sources is a sound decision and a necessary step if we want to effectively address the climate crisis."
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"instruction": "Categorize the following words into two teams.",
|
| 49 |
+
"input": "turtle, hedgehog, goat, fox",
|
| 50 |
+
"output": "Team 1: Turtle, Hedgehog\nTeam 2: Goat, Fox"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"instruction": "Explain to me how a computer virus can be contracted.",
|
| 54 |
+
"input": "",
|
| 55 |
+
"output": "Computer viruses can be contracted in various ways. Common methods include downloading files from untrustworthy websites, opening email attachments from unknown sources, and visiting websites that contain malicious code. Additionally, sharing unsecured computer systems with others can allow for the virus to spread from one system to another."
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"instruction": "Taking partial capital loss purely for tax purposes",
|
| 59 |
+
"input": "",
|
| 60 |
+
"output": "Note that the rules around wash sales vary depending on where you live. For the U.S., the wash sale rules say that you cannot buy a substantially identical stock or security within 30 days (before or after) your sale. So, you could sell your stock today to lock in the capital losses. However, you would then have to wait at least 30 days before purchasing it back. If you bought it back within 30 days, you would disqualify the capital loss event. The risk, of course, is that the stock's price goes up substantially while you are waiting for the wash sale period. It's up to you to determine if the risk outweighs the benefit of locking in your capital losses. Note that this applies regardless of whether you sell SOME or ALL of the stock. Or indeed, if we are talking about securities other than stocks."
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"instruction": "What is the best resource for determining a specific age-based asset allocation?",
|
| 64 |
+
"input": "",
|
| 65 |
+
"output": "Look into the asset allocations of lifecycle funds offered by a company like Vanguard. This page allows you to select your current age and find a fund based on that. You could pick a fund, like the Target Retirement 2055 Fund (ages 21-25), and examine its allocation in the Portfolio & Management tab. For this fund, the breakdown is: Then, look at the allocation of the underlying funds that comprise the lifecycle fund, in the same tab. Look at each of those funds and see what asset allocation they use, and that should give you a rough idea for an age-based allocation. For example, the Total Stock Market Index Fund page has a sector breakdown, so if you wanted to get very fine-grained with your allocation, you could. (You're probably much better off investing in the index fund, low-cost ETFs, or the lifecycle fund itself, however; it'll be much cheaper). Doing this for several lifecycle funds should be a good start. Keep in mind, however, that these funds are rebalanced as the target date approaches, so if you're following the allocation of some particular funds, you'll have to rebalance as well. If you really want an age-based allocation that you don't have to think about, invest in a lifecycle fund directly. You'll probably pay a lower expense ratio than if you invested in a whole slew of funds directory, and it's less work for someone who isn't comfortable managing their portfolio themselves. Furthermore, with Vanguard, the expense ratios are already fairly low. This is only one example of an allocation, however; your tolerance of risk, age, etc. may affect what allocation you're willing to accept. Full disclosure: Part of my Roth IRA is invested in the Target 2055 fund I used as an example above, and another part uses a similar rebalancing strategy to the one I used above, but with Admiral Share funds, which have higher minimum investments but lower expense ratios."
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"instruction": "How secure is my 403(b)? Can its assets be \u201craided\u201d?",
|
| 69 |
+
"input": "",
|
| 70 |
+
"output": "I assume you get your information from somewhere where they don't report the truth. I'm sorry if mentioning Fox News offended you, it was not my intention. But the way the question is phrased suggests that you know nothing about what \"pension\" means. So let me explain. 403(b) is not a pension account. Pension account is generally a \"defined benefit\" account, whereas 403(b)/401(k) and similar - are \"defined contribution\" accounts. The difference is significant: for pensions, the employer committed on certain amount to be paid out at retirement (the defined benefit) regardless of how much the employee/employer contributed or how well the account performed. This makes such an arrangement a liability. An obligation to pay. In other words - debt. Defined contribution on the other hand doesn't create such a liability, since the employer is only committed for the match, which is paid currently. What happens to your account after the employer deposited the defined contribution (the match) - is your problem. You manage it to the best of your abilities and whatever you have there when you retire - is yours, the employer doesn't owe you anything. Here's the problem with pensions: many employers promised the defined benefit, but didn't do anything about actually having money to pay. As mentioned, such a pension is essentially a debt, and the retiree is a debt holder. What happens when employer cannot pay its debts? Employer goes bankrupt. And when bankrupt - debtors are paid only part of what they were owed, and that includes the retirees. There's no-one raiding pensions. No-one goes to the bank with a gun and demands \"give me the pension money\". What happened was that the employers just didn't fund the pensions. They promised to pay - but didn't set aside any money, or set aside not enough. Instead, they spent it on something else, and when the time came that the retirees wanted their money - they didn't have any. That's what happened in Detroit, and in many other places. 403(b) is in fact the solution to this problem. Instead of defined benefit - the employers commit on defined contribution, and after that - it's your problem, not theirs, to have enough when you're retired."
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"instruction": "Why don't banks allow more control over credit/debit card charges?",
|
| 74 |
+
"input": "",
|
| 75 |
+
"output": "Credit cards and debit cards make up the bulk of the transactions in the US. Visa and Mastercard take a percentage of each credit card transaction. For the most part, this fee it built into the price of what you buy. That is, you don't generally pay extra at the grocery store if you use a credit card (gasoline purchases are a notable exception here.) If you were getting something like 2% of a third of all the retail transactions in the US, you'd probably not want to rock the boat too much either. Since there is little fraud relative to the amount of money they are taking in, and it can often be detected using statistical analysis, they don't really stand to gain that much by reducing it through these methods. Sure they can reduce the losses on the insurance they provide to the credit card consumer but they risk slowing down the money machine. These companies want avoid doing something like reducing fraud by 0.5% revenues but causing purchases with the cards drop by 1%. More security will be implemented as we can see with the (slow) introduction of chip cards in the US but only at a pace that will prevent disruption of the money machine. EMV will likely cause a large drop in CC fraud at brick-and-mortar stores but won't stop it online. You will likely see some sort of system like you describe rolled out for that eventually."
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"instruction": "Given a financial statement, predict what the stock market's reaction will be.",
|
| 79 |
+
"input": "The company reported a 25% growth in profits year-over-year.",
|
| 80 |
+
"output": "The stock market would likely react positively to the company's financial statement, resulting in a rise in stock prices."
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"instruction": "Write a poem about joy.",
|
| 84 |
+
"input": "",
|
| 85 |
+
"output": "Joy is in the smallest things\nThe sight of a sunset\u2019s wings\nThe song of a bird on a breeze\nThe laughter of children at ease\n\nJoy hides in the budding blooms\nLike secrets revealed in their tombs\nThe silence of a peaceful night\nThat looms beneath the stars so bright\n\nJoy is found in the dawn of day\nWhen the sun comes out to play\nIn the sparkle of the summer rain\nThat washes away every pain\n\nThis is joy, meant to be embraced\nFor it can fill any empty space\nLet it fill you up inside\nAnd cast away your tear-soaked pride."
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"instruction": "Create a list of five problems associated with air pollution.",
|
| 89 |
+
"input": "",
|
| 90 |
+
"output": "1. Harmful effects on human health\n2. Impacts on natural ecosystems\n3. Costly damages to infrastructure\n4. Contribution to global warming and climate change\n5. Deterioration of air quality leading to breathing difficulties for many."
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"instruction": "Given a word, construct its antonyms.",
|
| 94 |
+
"input": "Light",
|
| 95 |
+
"output": "Dark, heavy, dense"
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"instruction": "If NYSE has market makers, what is the role of NYSE ARCA which is an ECN",
|
| 99 |
+
"input": "",
|
| 100 |
+
"output": "I would say it's a bit more complicated than that. Do you understand what a market maker does? An ECN (electronic communication network) is a virtual exchange that works with market makers. Using a rebate structure that works by paying for orders adding liquidity and charges a fee for removing liquidity. So liquidity is created by encouraging what are essentially limit orders, orders that are outside of the current market price and therefore not immediately executable. These orders stay in the book and are filled when the price of the security moves and triggers them. So direct answer is NYSE ARCA is where market makers do their jobs. These market makers can be floor traders or algorithmic. When you send an order through your brokerage, your broker has a number of options. Your order can be sent directly to an ECN/exchange like NYSE ARCA, sent to a market making firm like KCG Americas (formerly Knight Capital), or internalized. Internalization is when the broker uses an in house service to execute your trade. Brokerages must disclose what they do with orders. For example etrade's. https://content.etrade.com/etrade/powerpage/pdf/OrderRouting11AC6.pdf This is a good graphic showing what happens in general along with the names of some common liquidity providers. http://www.businessweek.com/articles/2012-12-20/how-your-buy-order-gets-filled"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"instruction": "Compare and contrast zero knowledge proofs with digital signatures",
|
| 104 |
+
"input": "",
|
| 105 |
+
"output": "Zero knowledge proofs are a cryptographic technique that allow one party to prove to another that they know a specific piece of information without revealing the actual information. This process typically involves a prover and a verifier. The prover proves the validity of an assertion such as a password or digital signature without revealing the information, while the verifier verifies the prover\u2019s assertion. Digital signatures, on the other hand, are essentially messages, certificates, or documents that are signed with an encrypted algorithm that can be used to verify the authenticity of the sender. Digital signatures are used to ensure data security and integrity, while zero knowledge proofs help to ensure privacy."
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"instruction": "List 5 ways a machine learning model can be used to make decisions.",
|
| 109 |
+
"input": "",
|
| 110 |
+
"output": "1. Classifying data into predetermined categories.\n2. Forecasting the outcome of a given scenario.\n3. Anomaly detection in the data.\n4. Automating tedious decision-making processes.\n5. Recommending the best options to a user."
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"instruction": "Suggest 3 variations of a given recipe.",
|
| 114 |
+
"input": "Apple Pie",
|
| 115 |
+
"output": "Three variations of a traditional apple pie recipe are:\n- Apple Crumble Pie - layer sliced apples and cinnamon sugar in a baking dish and top with a crumble topping of flour, butter and sugar. \n- Apple Streusel Pie - layer sliced apples with cinnamon sugar, then top with a streusel topping of butter, flour, cinnamon and sugar. \n- Apple Caramel Pie \u2013 layer sliced apples with caramel sauce and sprinkle with crumble topping."
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"instruction": "Rewrite the following statement in the imperative form. \u201cPlease submit your review.\u201d",
|
| 119 |
+
"input": "",
|
| 120 |
+
"output": "Submit your review."
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"instruction": "Alternatives to Intuit's PayTrust service for online bill viewing and bill payment?",
|
| 124 |
+
"input": "",
|
| 125 |
+
"output": "(Six years later...) I've used CheckFree for over 20 years, and my uncle started using it back in the early 1980s through a 300 baud modem. It has e-bills, EDI bills that you schedule yourself, and will also mail checks to people and small businesses. You can make your payments from an unlimited number of banks, can schedule multiple recurring payments for the same bill (I find that useful for when buying large/expensive items by CC: I create a different payment schedule for each), plus ad hoc payments."
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"instruction": "Need to change cash to cashier's check without bank account (Just arrived to the US)",
|
| 129 |
+
"input": "",
|
| 130 |
+
"output": "A cashier's check costs money to get and is not connected to an account. You have cash. You should be able to get a bank to sell you one, even without an account. Find a bank where you would like to open an account and explain the situation. I can't guarantee that that will work, but I would expect it to do so. If not, the bank can probably suggest an alternative. You might also ask the landlord if you can do it with postal money orders. I am positive that you can buy those with cash. You might have to buy a bunch to reach your desired amount. Or perhaps a Western Union money order might be better. You also might be able to open an account with your passport and Social Security Number (SSN)."
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"instruction": "Name one possible consequence of this issue.",
|
| 134 |
+
"input": "Issue: The widening use of smartphone technology has led to an increase in cyberbullying.",
|
| 135 |
+
"output": "One possible consequence of this issue is that victims of cyberbullying can become emotionally distressed and experience decreased self-esteem and more severe mental health issues."
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"instruction": "Identify the most important cause of air pollution in the cities.",
|
| 139 |
+
"input": "",
|
| 140 |
+
"output": "The most important cause of air pollution in cities is emissions from vehicles, such as cars and buses."
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"instruction": "Using multiple bank accounts",
|
| 144 |
+
"input": "",
|
| 145 |
+
"output": "I live near historic Concord, Massachusetts, and frequently drive past Walden Pond. I'm reminded of Henry David Thoreau's words, \"Simplify, simplify, simplify.\" In my opinion, fewer is better. 2 checkbooks? I don't see how that makes budgeting any easier. The normal set of expenses are easily kept as one bucket, one account. The savings 2&3 accounts can also be combined and tracked if you really want to think of them as separate accounts. Now, when you talk about 'Retirement' that can be in tax-wise retirement accounts, e.g. 401(k), IRA, etc. or post tax regular brokerage accounts. In our situation, the Schwab non-retirement account was able to handle emergency (as money market funds) along with vacation/rainy day, etc, in CDs of different maturities. As an old person, I remember CDs at 10% or higher, so leaving money in lower interest accounts wasn't good. Cash would go to CDs at 1-5 year maturities to maximize interest, but keep money maturing every 6-9 months. Even with the goal of simplifying, my wife and I each have a 401(k), an IRA, and a Roth IRA, I also have an inherited Roth, and I manage my teen's Roth and brokerage accounts. That's 9 accounts right there. No way to reduce it. To wrap it up, I'd go back to the first 4 you listed, and use the #4 checking attached to the broker account to be the emergency fund. Now you're at 3. Any higher granularity can be done with a spreadsheet. Think of it this way - the day you see the house you love, will you not be so willing to give up that year's vacation?"
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"instruction": "Take 2 minutes for a mindfulness practice",
|
| 149 |
+
"input": "",
|
| 150 |
+
"output": "Begin by sitting comfortably and paying attention to your breath. Focus on the sensation of the air entering your nostrils and leaving your mouth. Count your inhalationbreaths from one to eight, then repeat. Do this entire process again, then focus on your entire body and release any tension that you may have. Finally, let your mind be still and peaceful, and when you're ready you may open your eyes."
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"instruction": "Describe a psychological condition called \"depersonalization disorder\".",
|
| 154 |
+
"input": "",
|
| 155 |
+
"output": "Depersonalization disorder is a form of dissociative disorder in which an individual experiences a feeling of being detached from reality or themselves. People with depersonalization disorder may feel as though their thoughts and emotions are not their own, and they may have difficulty recognizing faces and voices. They may also experience distortion in the perception of time and space. Symptoms can be intermittent or continuous, and the disorder is often associated with depression, anxiety, and post-traumatic stress disorder."
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"instruction": "Analyze the argument that global warming is a hoax",
|
| 159 |
+
"input": "",
|
| 160 |
+
"output": "The argument that global warming is a hoax is largely unsupported. Recent scientific findings indicate that the Earth is indeed warming at an alarming rate, and human activities are the major cause of this warming. On top of this, the only evidence that skeptics have presented to suggest that global warming is a hoax is anecdotal, and has not been verified by any scientific methodologies. Overall, the argument that global warming is a hoax is not supported by any reliable evidence."
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"instruction": "Explain the term 'network latency' in one sentence.",
|
| 164 |
+
"input": "",
|
| 165 |
+
"output": "Network latency is the amount of time it takes for data to be transferred between two points on a network."
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"instruction": "Decompose the following polygon into smaller polygons.",
|
| 169 |
+
"input": "A polygon with 5 sides",
|
| 170 |
+
"output": "The polygon can be decomposed into two triangles and a square."
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"instruction": "Provide a general overview of the given topic.",
|
| 174 |
+
"input": "Topic: The birth of e-commerce",
|
| 175 |
+
"output": "The birth of e-commerce is an important milestone, as it introduced a new era of seamless shopping and convenience to consumers. The idea of buying items online without having to leave the comfort of home first became possible in the mid-to-late 1990s, fueled by the widespread availability of the internet. Since then, e-commerce has grown rapidly and continues to expand, with numerous advancements in technologies such as online payment processing, smartphones, and virtual/augmented reality. Nowadays, e-commerce has become an integral part of people's lives, with a variety of goods and services available through digital channels."
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"instruction": "Classify this sentence as positive, negative or neutral sentiment: \"I am so tired\".",
|
| 179 |
+
"input": "",
|
| 180 |
+
"output": "Negative sentiment."
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"instruction": "Classify the following sentence as verb, noun, adjective, etc.",
|
| 184 |
+
"input": "Nature is beautiful.",
|
| 185 |
+
"output": "Noun (Nature), Adjective (beautiful)."
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"instruction": "Create a cryptographic code using a substitution cipher.",
|
| 189 |
+
"input": "",
|
| 190 |
+
"output": "A substitution cipher is a cryptographic code which replaces each letter in the plaintext with another letter. For example, a substitution cipher might replace each letter with the letter three positions after it in the alphabet. So, the letter \"A\" would be replaced with the letter \"D\", the letter \"B\" would be replaced with the letter \"E\", and so on."
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"instruction": "Describe the symptoms of a cold",
|
| 194 |
+
"input": "",
|
| 195 |
+
"output": "The symptoms of a cold include a sore throat, a headache, a runny nose, sneezing, congestion, a cough, body aches, fatigue, and fever. Other symptoms can include chills, loss of appetite, digestive issues, and a loss of smell and taste."
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"instruction": "Describe the flow of oxygen in the human body.",
|
| 199 |
+
"input": "",
|
| 200 |
+
"output": "The flow of oxygen in the human body begins with breathing. Oxygen is taken in through the lungs and enters the bloodstream. From the bloodstream, oxygen is transported to the cells throughout the body. \n\nOnce inside the cells, the oxygen combines with other molecules to create adenosine triphosphate (ATP), the energy source for all cell activity. The ATP is then used to power metabolic and physiological processes in the body. The oxygen that was used to make ATP is then released back into the bloodstream, and is eventually exhaled back out of the lungs."
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"instruction": "Where can I open a Bank Account in Canadian dollars in the US?",
|
| 204 |
+
"input": "",
|
| 205 |
+
"output": "Give Harris Bank a call; they might be able to help you As of August 21, 2015, Harris bank does NOT offer Canadian dollar accounts in the U.S."
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"instruction": "Are bonds really a recession proof investment?",
|
| 209 |
+
"input": "",
|
| 210 |
+
"output": "No, they are not recession proof. Assume several companies, that issued bonds in the fund, go bankrupt. Those bonds could be worthless, they could miss principle payments, or they could be restructured. All would mean a decline in value. When the economy shrinks (which is what a recession is) how does the Fed respond? By lowering interest rates. This makes current bonds more valuable as presumably they were issued at a higher rate, thus the recession proof prejudice. However, there is nothing to stop a company (in good financial shape) from issuing more bonds to pay the par value on high-interest bonds, thus refinancing their debt. Sort of like how the bank feels when one refinances the mortgage for a lower rate. The thing that troubles me the most is that rates have been low for a long time. What happens if we have a recession now? How does the Fed fix it? I am not sure exactly what the fallout would be, but it could be significant. If you are troubled, you should look for sectors that would be hurt and helped by a Trump-induced recession. Move money away from those that will be hurt. Typically aggressive growth companies are hurt (during recessions), so you may want to move money away from them. Typically established blue chip companies fare okay in a recession so you may want to move money toward them. Move some money to cash, and perhaps some towards bonds. All that being said, I'd keep some money in things like aggressive growth in case you are wrong."
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"instruction": "U.S. stock sales- tax on sale for NR Canadian",
|
| 214 |
+
"input": "",
|
| 215 |
+
"output": "If you're a non resident then you owe no capital gains tax to Canada. Most banks won't let you make trades if you're a non-resident. They may not have your correct address on file so they don't realize this. This is not tax law but just OSC (or equivalent) regulations. You do have to fill out paperwork for withholding tax on OAS/CPP payments. This is something you probably already do but here's a link . It's complicated and depends on the country you live in. Of course you may owe tax in Thailand, I don't know their laws."
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"instruction": "Name three things associated with the topic.",
|
| 219 |
+
"input": "Hiking",
|
| 220 |
+
"output": "Hiking is associated with walking, enjoying nature, and exploring trails."
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"instruction": "Provide three example usages of the word 'adorn'.",
|
| 224 |
+
"input": "",
|
| 225 |
+
"output": "1. The bride adorned herself with the finest jewels. \n2. The house was adorned with holiday decorations. \n3. She adorned her wedding dress with a beautiful sash."
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"instruction": "What is the difference between \u201cgood debt\u201d vs. \u201cbad debt\u201d?",
|
| 229 |
+
"input": "",
|
| 230 |
+
"output": "First of all debt is a technology that allows borrower to bring forward their spending; it's a financial time machine. From borrowers point of view debt is good when it increases overall economic utility. A young person wants to bring up a family but cannot afford the house. Had they waited for 30 years they would have reached the level of income and savings to buy the house for cash. By the time it might be too late to raise a family, sure they'd enjoy the house for the last 20 years of their life. But they would loose 30 years of utility - they could have enjoyed the house for 50 years! So, for a reasonable fee, they can bring the spending forward. Another young person might want to enjoy a life of luxury, using the magical debt time machine and bringing forward their future earnings. They might spend 10 years worth of future earnings on entertainment within a year and have a blast. Due to the law of diminishing marginal utility - all that utility is pretty much wasted, but they'll still will need to make sacrifices in the future. The trick is to roughly match the period of debt repayment to the economic life of the purchase. Buying a house means paying over 30 years for an asset that has an economic life of 80 years+, given that the interest fee is reasonable and the house won't loose it's value overnight that's a good debt. Buying a used car with a remaining life of 5 years and financing its with a seven years loan - is not a good idea. Buying a luxurious holiday that lasts a fortnight with 2 years of repayments, i.e. financing non-essential short term need with medium term debt is insane. The other question is could the required utility be achieved through a substitute at a lower cost without having to bring the spending forward or paying the associated fee."
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"instruction": "How can you possibly lose on investments in stocks?",
|
| 234 |
+
"input": "",
|
| 235 |
+
"output": "If you're talking about a single stock, you greatly underestimate the chances of it dropping, even long-term. Check out the 12 companies that made up the first Dow Jones Industrial Average in 1896. There is probably only one you've heard of: GE. Many of the others are long gone or have since been bought up by larger companies. And remember these were 12 companies that were deemed to be the most representative of the stock market around the turn of the 20th century. Now, if you're talking about funds that hold many stocks (up to thousands), then your question is a little different. Over the long-term (25+ years), we have never experienced a period where the overall market lost value. Of course, as you recognize, the psychology of investors is a very important factor. If the stock market loses half of its value in a year (as it has done a few times), people will be inundated with bad news and proclamations of \"this time it's different!\" and explanations of why the stock market will never recover. Perhaps this may be true some day, but it never has been thus far. So based on all the evidence we have, if you hold a well-diversified fund, the chances of it going down long-term (again, meaning 25+ years) are basically zero."
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"instruction": "Name the top 5 TV shows that have the highest IMDB rating?",
|
| 239 |
+
"input": "",
|
| 240 |
+
"output": "The top 5 TV shows according to IMDb rating are \"The Wire\" (9.3), \"Breaking Bad\" (9.5), \"Game of Thrones\" (9.4), \"The Sopranos\" (9.2), and \"Band of Brothers\" (9.4)."
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"instruction": "Using the provided condition, construct a query in SQL.",
|
| 244 |
+
"input": "SELECT column FROM table WHERE condition = true",
|
| 245 |
+
"output": "SELECT column FROM table WHERE condition = true AND gender = 'male'"
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"instruction": "Identify what type of relationship exists between two items.",
|
| 249 |
+
"input": "X-ray and Scan",
|
| 250 |
+
"output": "X-ray and Scan are both medical imaging procedures used to create a visual representation of the body. The difference between them is that an X-ray uses radiation to create the image, while a Scan uses sound waves."
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"instruction": "Generate a list of 10 strategies for dealing with stress.",
|
| 254 |
+
"input": "",
|
| 255 |
+
"output": "- Identify the sources of stress in your life.\n- Develop healthy coping strategies.\n- Exercise regularly.\n- Get enough sleep.\n- Eat a healthy diet.\n- Practice deep breathing exercises.\n- Spend time with friends and family.\n- Learn to say \u201cno\u201d.\n- Take a break from the situation that\u2019s causing you stress.\n- Find ways to reduce unnecessary commitments."
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"instruction": "Write a new sentence that has a simile in it.",
|
| 259 |
+
"input": "",
|
| 260 |
+
"output": "Her laughter was like a light rain shower on a hot summer day."
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"instruction": "Given a news article, explain the key points and implications.",
|
| 264 |
+
"input": "News Article: Google is launching a new cloud-computing service.",
|
| 265 |
+
"output": "Google has announced the launch of a new cloud-computing service, which will offer users a wide range of data storage and computing capabilities. This new service could provide businesses with a more cost-effective way of storing and accessing large amounts of data. It could also lead to the development of new applications and services that are accessed through the cloud."
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"instruction": "Come up with a riddle based on the given semantic keywords related to money.",
|
| 269 |
+
"input": "Hoarding, Spend, Bill",
|
| 270 |
+
"output": "What is always hoarded, but must be spent on bills? \nAnswer: Money."
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"instruction": "Identify the main theme of the following poem:\n\n\"I'd climb a hill so high,\nThat I could touch the stars in the sky,\nAnd I'd watch the birds fly by\"",
|
| 274 |
+
"input": "",
|
| 275 |
+
"output": "The main theme of the poem is ambition and determination. The speaker is expressing a desire to reach for their dreams."
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"instruction": "Generate a creative title that summarises the following article.",
|
| 279 |
+
"input": "A new report suggests that an increase in carbon dioxide levels has led to higher temperatures, more extreme weather events and a rapid decline in biodiversity.",
|
| 280 |
+
"output": "The Perils of Increased Carbon: A Rapid Climatic and Biodiversity Crisis."
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"instruction": "Write an algorithm for solving an anagram.",
|
| 284 |
+
"input": "",
|
| 285 |
+
"output": "1. Get the anagram as input.\n2. Split the anagram into individual characters.\n3. Sort the characters in alphabetical order.\n4. Compare the sorted characters with words in a dictionary to form matches.\n5. Return the matches."
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"instruction": "Develop a plan for the use of a new software.",
|
| 289 |
+
"input": "Software: Customer Service Ticket System",
|
| 290 |
+
"output": "A plan for the use of a customer service ticket system should include: creating user accounts and assigning access levels, training staff on the system, mapping out customer service workflows, creating customer profiles, assigning a unique ticket number for each inquiry, tracking corresponding conversations and activities, and setting up automated customer service reports."
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"instruction": "Generate a list of activities for a birthday party for a 9-year-old.",
|
| 294 |
+
"input": "",
|
| 295 |
+
"output": "Here is a list of activities for a 9-year-old's birthday party:\n- Have a treasure hunt: Hide small items around the house or outside and have the children search for them. \n- Have a potato-sack race: Divide the children into two teams and let them race each other while carrying a sack of potatoes. \n- Balloon relay: Divide the group into two teams and have them compete in a balloon popping race. \n- Build castles out of blocks: Provide a variety of blocks to the children and let them build creative structures. \n- Have karaoke: Set up a karaoke machine and sing along to the children's favorite songs. \n- Scavenger hunt: Hide items around the house and have the kids find them. \n- Have a themed party: Take the birthday boy or girl's favorite character and make it the theme of the entire house by decorating with the character's items. \n- Paint a mural: Give the children paint and brushes and let them create a mural together. \n- Have a scavenger hunt in the park: Hide items in the park and have the children hunt them down. \n- Play hide and seek: Let the children play hide and seek in the house or in the park. \n- Make a fashion show: Provide clothing items such as hats and jewelry and let the children play dress up and model their outfits. \n- Make a pinata: Provide a pinata filled with candy and let the children take turns in hitting it to break it open."
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"instruction": "Find the minimum value of the function",
|
| 299 |
+
"input": "f(x, y) = 2x^2 + y^3",
|
| 300 |
+
"output": "The minimum value of the function f(x, y) = 2x^2 + y^3 is 0, when x = 0 and y = 0."
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"instruction": "How do I know when I am financially stable/ready to move out on my own?",
|
| 304 |
+
"input": "",
|
| 305 |
+
"output": "It all depends on what your financial goals are when you are ready. You sound like you could be ready today if you wanted to be. The steps that I would take are. Create a monthly draft budget. This doesn't have to be something hard and fast, just a gague of what your living expenses would be compared to your after-tax salary. Make sure there would be room for \"fun\" money. a. Consider adding a new car fund line item to this budget, and deducting that amount from your paycheck starting now so that you can save for the car. Based on the most realistic estimate that you can make, you'll get a good idea if you want to spend the money it takes to move out alone now or later. You'll also see the price for various levels of rentals in your area (renting a single family home, townhouse, condo, apartment, living in a rented room or basement, sharing a place with friends, etc) and know some of the costs of setting up for yourself. Since you're looking at the real estate market, you may want to do a cost comparison of renting versus buying. I've found the New York Times interactive graphic on this is excellent. If you are looking to buy, make sure to research the hidden costs of buying thoroughly before taking this step. To answer your last question, if you have the cash you should consider upping your 401K investment (or using Roth or regular IRA). Make sure you are investing enough to get your full employer match, if your employer offers one, and then get as close as you can to government maximum contribution limits. Compound interest is a big deal when you are 23."
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"instruction": "classify the given phrases as either a fact or opinion.",
|
| 309 |
+
"input": "The United States is the world's largest economy.\nBananas are the best fruit.",
|
| 310 |
+
"output": "The United States is the world's largest economy: Fact\nBananas are the best fruit: Opinion"
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"instruction": "Identify the country in which the World Trade Center is located.",
|
| 314 |
+
"input": "",
|
| 315 |
+
"output": "The World Trade Center is located in New York City, United States."
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"instruction": "How do you translate a per year salary into a part-time per hour job?",
|
| 319 |
+
"input": "",
|
| 320 |
+
"output": "If you're really a part-time worker, then there are some simple considerations.... The remote working environment, choice of own hours, and non-guarantee of work availability point to your \"part-time\" situation being more like a consultancy, and that would normally double or triple the gross hourly rate. But if they're already offering or paying you a low hourly figure, they are unlikely to give you consultant rates."
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"instruction": "Write an essay about the societal implications of artificial intelligence",
|
| 324 |
+
"input": "",
|
| 325 |
+
"output": "The increasing prevalence of artificial intelligence (AI) has caused a wave of discussions about its implications for our society. AI systems are becoming capable of carrying out tasks both quickly and accurately, and they have the potential to revolutionize the way humans interact with machines. However, while AI has the potential to provide immense benefits, the rise of AI is not without its risks. AI decision-making can often be biased and opaque, while more sophisticated AI models can lead to complex societal and ethical issues. In our increasingly digital world, it is important to be aware of the implications that AI and machine learning can have on our individual lives, as well as on society as a whole."
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"instruction": "Generate a summation of the following numbers",
|
| 329 |
+
"input": "1,2,3,4,5",
|
| 330 |
+
"output": "Summation of the numbers is 15"
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"instruction": "Describe the recipe for the following dish.",
|
| 334 |
+
"input": "Tom Yum Soup",
|
| 335 |
+
"output": "Tom Yum Soup is an aromatic and flavorful Thai soup made with lemongrass, kaffir lime leaves, galangal, mushrooms, and a spicy chili paste. The broth is seasoned with fish sauce, tamarind paste, and lime juice to create an uplifting and spicy appetizer."
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"instruction": "Name three political leaders from Russia.",
|
| 339 |
+
"input": "",
|
| 340 |
+
"output": "Three political leaders from Russia are Vladimir Putin, Dmitry Medvedev and Sergey Shoygu."
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"instruction": "Rewrite the following phrase in its interrogative form",
|
| 344 |
+
"input": "Life is beautiful",
|
| 345 |
+
"output": "Is life beautiful?"
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"instruction": "Given a type of food, create a recipe for it.",
|
| 349 |
+
"input": "apple crumble",
|
| 350 |
+
"output": "Apple Crumble Recipe\nIngredients: \n- 4 large apples, peeled, cored and cubed \n- 2 tablespoons of butter \n- \u00bd cup of packed brown sugar \n- 2 tablespoons of all-purpose flour \n- \u00bd teaspoon of ground cinnamon \n- \u00bc teaspoon of ground nutmeg \n- 1 cup of rolled oats \n\nInstructions:\n1. Preheat oven to 350 degrees F (175 degrees C). Grease an 8-inch square baking dish.\n2. Spread the cubed apples in the greased baking dish. \n3. In a medium bowl, mix together the butter, brown sugar, flour, cinnamon, nutmeg and oats until well combined. \n4. Sprinkle the oat crumble mixture over the apples and spread evenly.\n5. Bake in preheated oven for about 30 minutes, or until the top is golden brown. Serve warm."
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"instruction": "Explain the principle of least action.",
|
| 354 |
+
"input": "",
|
| 355 |
+
"output": "The principle of least action states that the path taken between two points by a physical system is the one that requires the least amount of action. In other words, the path taken results in a net zero action between two points. This means that the action or energy is conserved over the duration of the path taken by the physical system. This principle has been used to explain the behavior of both light and sound waves in the natural world."
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"instruction": "Are Target Funds Unsafe - Post Q.E.?",
|
| 359 |
+
"input": "",
|
| 360 |
+
"output": "It's a what-if? sort of question. What if rates stay down or trend only slightly higher, despite no QE? look at other countries response to tepid economies. My experience as professional advisor (25 yrs) tells me the future is unknowable and diversity is good. Make alternative choices- they all won't work wonderfully, but some will."
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"instruction": "Name three strengths you have and explain how they will benefit the company if you were to be hired.",
|
| 364 |
+
"input": "",
|
| 365 |
+
"output": "I possess three strengths which I believe will be of great benefit to the company if I were to be hired. \n\nFirst, I have outstanding communication skills. I have an ability to communicate complex ideas and concepts in a simple and clear manner. I am also an active listener, which allows me to understand the needs and opinions of both my superiors and colleagues.\n\nSecond, I have great problem-solving skills which enable me to quickly identify the root cause of an issue and come up with creative solutions. I am also a creative thinker, who can think outside of the box and come up with innovative ideas.\n\nFinally, I have strong organizational and managerial skills. I am able to prioritize tasks, delegate responsibilities, and manage my time in order to hit deadlines. I also have experience leading and motivating teams and individual contributors to ensure that the tasks are completed on time and with the highest quality.\n\nI believe that these strengths will be of great value and benefit to the company if I were to be hired. I am confident that I can utilize them to help the company achieve its goals."
|
| 366 |
+
},
|
| 367 |
+
{
|
| 368 |
+
"instruction": "Will a credit card issuer cancel an account if it never incurs interest?",
|
| 369 |
+
"input": "",
|
| 370 |
+
"output": "Some years ago a call center operator told me a bit more than they probably should have. They like to see a lot of money go through the card, but very little staying on the card. Yes, they make money on the interest but one card defaulting blows away the profit on a lot of other cards. The 3% take from the merchants is both reliable and up-front, not 6 months down the line when (and if) you pay the interest. So if you want to make your credit card company happy, pay your bills in full every month. I have credit far beyond my actual means because I run work expenses on my personal card, I was told they didn't care (and had already guessed) that it wasn't my money. The point was I was handling things in a way they liked. Not quite at Palladium status, but cards with $200 annual fees are mine for the asking, and I haven't paid interest since the early 1990's."
|
| 371 |
+
},
|
| 372 |
+
{
|
| 373 |
+
"instruction": "Fix the following punctuation and spelling errors.",
|
| 374 |
+
"input": "We went outside for recess saturday",
|
| 375 |
+
"output": "We went outside for recess Saturday."
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"instruction": "Take the given sentences and make a new sentence with them using a colon for emphasis.",
|
| 379 |
+
"input": "He was very excited. He was looking forward to the new adventure.",
|
| 380 |
+
"output": "He was very excited: he was looking forward to the new adventure."
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"instruction": "What would I miss out on by self insuring my car?",
|
| 384 |
+
"input": "",
|
| 385 |
+
"output": "You're trading a fixed liability for an unknown liability. When I graduated from college, I bought a nice used car. Two days later, a deer came out of nowhere, and I hit it going 70 mph on a highway. The damage? $4,500. If I didn't have comprehensive insurance, that would have been a real hit to me financially. For me, I'd rather just pay the modest cost for the comprehensive."
|
| 386 |
+
},
|
| 387 |
+
{
|
| 388 |
+
"instruction": "Determine the base and the height of a triangle given its area.",
|
| 389 |
+
"input": "Area: 20 cm2",
|
| 390 |
+
"output": "Base: 10 cm, Height: 8.94 cm."
|
| 391 |
+
},
|
| 392 |
+
{
|
| 393 |
+
"instruction": "Assume you are a researcher. Explain the importance of conducting a survey.",
|
| 394 |
+
"input": "",
|
| 395 |
+
"output": "Surveys are an important tool for researchers as they help to collect large amounts of data in a short period of time. Surveys provide insight into the attitudes and behaviors of respondents, as well as the prevalence of certain attitudes and behaviors in specific groups. They can help to identify trends and attitudes that might otherwise be overlooked. Surveys can also be used to measure the effectiveness of interventions, compare outcomes across populations, and identify underlying causes of certain behaviors."
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"instruction": "Put on a put option",
|
| 399 |
+
"input": "",
|
| 400 |
+
"output": "If you look at it from the hedging perspective, if you're unsure you're going to need to hedge but want to lock in an option premium price if you do need to do so, I could see this making sense."
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"instruction": "Rewrite the following sentence to make it stronger:\n\nThe students are excited about their upcoming assignment.",
|
| 404 |
+
"input": "",
|
| 405 |
+
"output": "The students are thrilled with anticipation for their upcoming assignment."
|
| 406 |
+
},
|
| 407 |
+
{
|
| 408 |
+
"instruction": "Find and provide two sources to support the following statement",
|
| 409 |
+
"input": "The economic cost of climate change is estimated in trillions of dollars.",
|
| 410 |
+
"output": "Sources: \n1. https://www.weforum.org/agenda/2017/05/climate-change-economic-costs-billion-dollars/\n2. https://www.iea.org/news/global-cost-of-climate-change-could-reach-2-trillion-by-2030-report-finds"
|
| 411 |
+
},
|
| 412 |
+
{
|
| 413 |
+
"instruction": "What happens if I just don't pay my student loans?",
|
| 414 |
+
"input": "",
|
| 415 |
+
"output": "employed under the table and doesn't have a bank account If I could make that size 10,000,000 font I would. Your friend likely also isn't paying taxes. The student loan penalties will be nothing compared to what the IRS does to you. Avoid taking financial advice from that person."
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"instruction": "Can used books bought off Amazon be claimed as a tax deduction in Australia?",
|
| 419 |
+
"input": "",
|
| 420 |
+
"output": "Yes, if they meet the ATO's criteria. Books, periodicals and digital information If the item cost less than $300 you can claim an immediate deduction where it satisfies all of the following requirements: http://www.ato.gov.au/Individuals/Income-and-deductions/Deductions-you-can-claim/Other-deductions/Books,-periodicals-and-digital-information/ Alternatively They may be a self-education expense http://www.ato.gov.au/Individuals/Income-and-deductions/Deductions-you-can-claim/Self-education-expenses/ A Further Alternative They could fall into the tool, equipment or other asset category if they are for a professional library (this can include a home office). http://www.ato.gov.au/Individuals/Income-and-deductions/Deductions-you-can-claim/Tools,-equipment-and-other-assets/ I understand this is an old question although given the dead link in the above answer and the new resources this answer might prove helpful for others coming across this question."
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"instruction": "Classify the following color into warm and cool colors.",
|
| 424 |
+
"input": "Red",
|
| 425 |
+
"output": "Red is a warm color."
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"instruction": "Give a brief overview of Machine Learning.",
|
| 429 |
+
"input": "",
|
| 430 |
+
"output": "Machine Learning is the process of creating algorithms that can learn from data to make decisions and predictions. It is used in a wide range of applications such as image recognition, natural language processing, speech recognition, robotics, and medical diagnostics. Machine Learning algorithms are designed to improve themselves over time by identifying patterns in data and making decisions based on those patterns. Machine Learning has become increasingly popular in recent years as new algorithms and technologies have allowed for faster and more accurate predictions and decisions to be made."
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"instruction": "Match the given words to their respective descriptions?",
|
| 434 |
+
"input": "Structured Query Language: Database Management System",
|
| 435 |
+
"output": "Structured Query Language (SQL): Database Management System - SQL is a standard programming language that is used to interact with a relational database management system (RDBMS) to store, retrieve, and manipulate data. It is a declarative language that allows the user to specify what information they want from the database and allows the database to optimize the communication between the user and the database."
|
| 436 |
+
},
|
| 437 |
+
{
|
| 438 |
+
"instruction": "Getting Cash from Credit Card without Fees",
|
| 439 |
+
"input": "",
|
| 440 |
+
"output": "You said: Use a credit card (to get my 3% Cash back) to withdraw cash ... Then you said: Is there any way to do this without paying a cash advance fee (or any fees in general)? Right there you have stated the inconsistency. Withdrawing cash using a credit card is a cash advance. You may or may not be charged a fee for doing the cash advance, but no credit card will offer you cash back on a cash advance, so you can't earn your 3% by using cash advances. As others have mentioned, you can sometimes get close by using the card to purchase things that are almost like cash, such as gift cards. But you have to make a purchase."
|
| 441 |
+
},
|
| 442 |
+
{
|
| 443 |
+
"instruction": "Compare and contrast the characteristics of the desert and the jungle.",
|
| 444 |
+
"input": "",
|
| 445 |
+
"output": "Deserts are usually dry and have sparse vegetation, while jungles are usually wet and have dense vegetation. Desert landscapes can vary from sand dunes to mountains, while jungles are typically covered in trees and other vegetation. The temperature and weather in the desert tends to be very hot and arid, while the jungle is typically warm and humid."
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"instruction": "Create a resume objective statement for a person seeking a position as a software engineer.",
|
| 449 |
+
"input": "",
|
| 450 |
+
"output": "A passionate software engineer seeking to leverage over 5 years of experience in designing and developing complex software applications in order to improve the performance and user experience of highly trafficked websites."
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"instruction": "What is a good way to handle a difficult problem?",
|
| 454 |
+
"input": "",
|
| 455 |
+
"output": "A good way to handle a difficult problem is to take a step back and identify the root cause. Once you have identified the underlying cause, you can consider different solutions, weigh them carefully, and determine the best course of action. Additionally, it is important to gather the necessary resources needed to address the problem and stay organized."
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"instruction": "Compose a blog article on the importance of exercise.",
|
| 459 |
+
"input": "",
|
| 460 |
+
"output": "Exercise is essential to living a healthy and active lifestyle. Regular exercise helps to improve physical strength and endurance, increase energy levels, improve cardiovascular health and lower risk of certain diseases. In addition, studies have also shown that exercise releases endorphins which can help to reduce stress and anxiety. Exercise can also help to build mental resilience, concentration, and focus.\n\nGetting started with an exercise routine does not have to be daunting. It can be as simple as a 10-minute walk or jog around the block and gradually increasing the intensity and duration as you get more comfortable with exercise. Regular physical activity can also be fun and your exercise routine can be tailored to your own personal interests. Whether it\u2019s going for a swim, running, biking, playing basketball or weight training, there is something for everyone.\n\nExercise is an important part of living a healthy and happy life, and it can have a great impact on both physical and mental wellbeing. So make sure to make time for regular physical activity to stay healthy and happy!"
|
| 461 |
+
},
|
| 462 |
+
{
|
| 463 |
+
"instruction": "Recent college grad. Down payment on a house or car?",
|
| 464 |
+
"input": "",
|
| 465 |
+
"output": "When I was in that boat a few years ago, I went for the car first. My thoughts: If I get the car first, I'm guaranteed to have a car that runs well. That makes it more convenient to commute to any job, or for social functions. I ended up dropping about $20k into a car (paid cash, I don't like being in debt). I chose to buy a really nice car, knowing it will last for many years to come - I'm expecting to not replace it for about 10 years from the purchase. I would urge you to consider paying in full for the car; dumping $20k+ is a lot, and there are plenty of nice cars out there in the $10-20k range that will work just fine for years to come. One benefit of paying in full is that you don't have a portion of your income tied into the car loan. The main reason I chose not to go for the house first had more to do with the difference in commitment. A home mortgage is a 30-year commitment on a large chunk of your income. With the job market and housing markets both currently working against you, it's better to wait until you have a large safety net to fall into. For example, it's always recommended to have several months worth of living expenses in savings. Compared to renting, having 6 or more months of mortgage payments + utilities + insurance + property taxes + other mandatory expenses (see: food, gas) comes out to a significant amount more that you should have saved (for me, I'm looking at a minimum of about $20k in savings just to feel comfortable; YMMV). Also, owning a house always has more maintenance costs than you will predict. Even if it's just replacing a few light bulbs at first, eventually you'll need something major: an appliance will die, your roof will spring a leak, anything (I had both of those happen in the first year, though it could be bad luck). You should make sure that you can afford the increased monthly payments while still well under your income. Once you're locked in to the house, you can still set aside a smaller chunk of your income for a new car 5-10 years down the road. But if you're current car is getting down to it's last legs, you should get that fixed up before you lock yourself in to an uncomfortable situation. Don't be in too much of a hurry to buy a house. The housing market still has a ways to go before it recovers, and there's not a whole lot to help it along. Interest rates may go up, but that will only hurt the housing market, so I don't expect it to change too much for the next several months. With a little bit of sanity, we won't have another outrageous housing bubble for many years, so houses should remain somewhat affordable (interest rates may vary). Also keep in mind that if you pay less that 20% down on the house, you may end up with some form of mortgage interest, which is just extra interest you'll owe each month."
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"instruction": "Edit the following sentence to ensure it is grammatically correct and properly punctuated.",
|
| 469 |
+
"input": "The town was founded in the 1700s",
|
| 470 |
+
"output": "The town was founded in the 1700's."
|
| 471 |
+
},
|
| 472 |
+
{
|
| 473 |
+
"instruction": "income tax for purchased/sold short term & long term shares",
|
| 474 |
+
"input": "",
|
| 475 |
+
"output": "As mentioned by Dilip, you need to provide more details. In general for transacting on stocks; Long Term: If you hold the stock for more than one year then its long term and not taxable. There is a STT [Securities Transaction Tax] that is already deducted/paid during buying and selling of a stock. Short Term: If you hold the stock for less than one year, it's short term gain. This can be adjusted against the short term loss for the financial year. The tax rate is 10%. Day Trading: Is same as short term from tax point of view. Unless you are doing it as a full time business. If you have purchased multiple quantities of same stock in different quantities and time, then when you selling you have to arrive at profit or loss on FIFO basis, ie First in First Out"
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"instruction": "Generate a list of landscape gardening projects for a garden that is 30m by 20m.",
|
| 479 |
+
"input": "Garden size: 30m by 20m",
|
| 480 |
+
"output": "1. Install a pergola for shade and comfort.\n2. Set up garden lighting for nighttime blooms and show. \n3. Set up raised flower gardens with different varieties of plants.\n4. Install a water feature such as a fountain or pond.\n5. Plant trees to provide shade and natural beauty.\n6. Plant climbing vines on fences and walls.\n7. Set up a vegetable garden.\n8. Construct a path of stones and gravel.\n9. Add a seating area with outdoor furniture.\n10. Utilize vertical lattices or garden arches to create a layered look and texture."
|
| 481 |
+
},
|
| 482 |
+
{
|
| 483 |
+
"instruction": "How does the value of an asset (valued in two different currencies) change when the exchange rate changes?",
|
| 484 |
+
"input": "",
|
| 485 |
+
"output": "Gold is traded on the London stock exchange (LSE) and the New York stock exchange (NYSE) under various separate asset tickers, mainly denominated in sterling and US dollars respectively. These stocks will reflect FX changes very quickly. If you sold LSE gold and foreign exchanged your sterling to dollars to buy NYSE gold you would almost certainly lose on the spreads upon selling, FX'ing and re-buying. In short, the same asset doesn't exist in multiple currencies. It may have the same International Securities Identification Number (ISIN), but it can trade with different Stock Exchange Daily Official List (SEDOL) identifiers, reflecting different currencies and/or exchanges, each carrying a different price at any one time."
|
| 486 |
+
}
|
| 487 |
+
]
|
data/training_data_20251102_220943.json
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"instruction": "What is the area of a triangle with base of 6 units and height of 9 units?",
|
| 4 |
+
"input": "",
|
| 5 |
+
"output": "The area of the triangle is 27 units."
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"instruction": "Describe the causes and symptoms of depression.",
|
| 9 |
+
"input": "",
|
| 10 |
+
"output": "Depression is caused by a combination of genetic, environmental, emotional, and interpersonal factors. The main symptoms may include sadness, lack of motivation, fatigue, difficulty concentrating, changes in appetite and sleep patterns, low self-esteem, and thoughts of suicide."
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"instruction": "Design an icon for a smartphone application.",
|
| 14 |
+
"input": "Name of Application: Health Monitor",
|
| 15 |
+
"output": "A visually attractive and eye-catching icon design for Health Monitor. The design features a graphic of a heart concentric to a drop of water, with varying shades of blue and green. The colors represent a healthy, vibrant life. The design conveys the idea of staying healthy and active with a modern and unique look."
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"instruction": "Create a pastry dish with the ingredients given.",
|
| 19 |
+
"input": "Pecans, cinnamon, honey",
|
| 20 |
+
"output": "Honey Pecan Cinnamon Rolls - a sweet and simple pastry made with pecans, cinnamon, and honey. The pecans add a nutty flavor and crunch, while the honey and cinnamon provide a warm sweetness that melts in your mouth."
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"instruction": "Provide three examples of technologies in artificial intelligence",
|
| 24 |
+
"input": "",
|
| 25 |
+
"output": "1. Natural language processing (NLP) \n2. Machine Learning \n3. Speech Recognition"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"instruction": "Name two US Presidents that served in the 20th century.",
|
| 29 |
+
"input": "",
|
| 30 |
+
"output": "Ronald Reagan and Bill Clinton."
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"instruction": "Write a one-sentence advertisement for a computer store.",
|
| 34 |
+
"input": "",
|
| 35 |
+
"output": "Come to our store for the latest computers, tablets, and accessories at unbeatable prices!"
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"instruction": "Google free real-time stock quotes",
|
| 39 |
+
"input": "",
|
| 40 |
+
"output": "Previously, Google had a delayed update for their stock prices (15 minutes I believe). That change enabled users of Google Finance to see updates to stock prices in real-time."
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"instruction": "Brainstorm ideas for a new business.",
|
| 44 |
+
"input": "",
|
| 45 |
+
"output": "Starting a business can be an exciting adventure, but it requires a lot of planning. Here are some ideas to consider when thinking about a new business venture:\n\n- Start a delivery business that helps local restaurants reach customers.\n- Create an online marketplace that specializes in vintage goods.\n- Launch a content marketing agency to help businesses build an online presence.\n- Open a boutique bakery that focuses on vegan and gluten-free desserts.\n- Build an app that connects homeowners with local handymen. \n- Develop a web-based software program to manage documents and paperwork. \n- Become a virtual assistant and provide online services to entrepreneurs. \n- Launch a subscription box service that ships snacks and treats from around the world. \n- Open a bike tour business that allows tourists to explore the city.\n- Create an online store that specializes in eco-friendly lifestyle products."
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"instruction": "Do Banks Cause Inflation? What are other possible causes?",
|
| 49 |
+
"input": "",
|
| 50 |
+
"output": "There are several causes of inflation. One is called cost push \u2014 that is, if the price of e.g. oil goes up sharply (as it did in the 1970s), it creates inflation by making everything cost more. Another is called demand pull: if labor unions bargain for higher wages (as they did in the 1960s), their wage costs push up prices, especially after they start buying. The kind of inflation that the banks cause is monetary inflation. That is, for every dollar of deposits, they can make $5 or $10 of loans. So even though they don't \"print\" money (the Fed does) it's as if they did. The result could be the kind of inflation called \"too much money chasing too few goods.\""
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"instruction": "What are the five steps of problem solving?",
|
| 54 |
+
"input": "",
|
| 55 |
+
"output": "The five steps of problem solving are: identifying the problem, understanding the problem, collecting relevant information, brainstorming and evaluating alternatives, and selecting and implementing the best solution."
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"instruction": "Write a few sentences to explain why marketing plays an important role in business.",
|
| 59 |
+
"input": "",
|
| 60 |
+
"output": "Marketing plays an essential role in any business as it helps in growing awareness of the company's products and services. Through effective marketing, companies can create demand for their products and services and build customer loyalty. Marketing also helps businesses differentiate themselves from their competitors and keep their customers engaged. Additionally, marketing helps establish a company's brand in the market and allows consumers to connect with the company's mission and values."
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"instruction": "Generate 5 different names for a bar.",
|
| 64 |
+
"input": "",
|
| 65 |
+
"output": "The Bubble, Maron's Tavern, The Foggy Mug, The Midnight Ale House, Donahue's Pub."
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"instruction": "Describe the steps required to build a software application.",
|
| 69 |
+
"input": "",
|
| 70 |
+
"output": "Building a software application typically involves six main steps: (1) understanding the problems and goals of the application, (2) preparing the architecture and design of the application, (3) developing the different components and modules, (4) testing and debugging the application, (5) deploying and integrating the application, and (6) maintaining and updating the application."
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"instruction": "Find an open-ended question that explores the impact of machine learning on the healthcare industry.",
|
| 74 |
+
"input": "",
|
| 75 |
+
"output": "How can machine learning be used to improve the effectiveness of healthcare treatments?"
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"instruction": "Imagine that you walk into a cafe to have a cup of coffee. Describe the experience in less than 100 words.",
|
| 79 |
+
"input": "",
|
| 80 |
+
"output": "Walking into the cafe, I was immediately struck by the warm atmosphere. Everywhere I looked, people were chatting over coffee or working on laptops. There was a pleasant aroma of freshly-brewed coffee in the air which made me feel relaxed. I stepped up to the counter to order my coffee, and was pleasantly surprised by the selection of exotic coffee blends. I found a comfortable spot to enjoy my coffee and watched the people around me. With its cozy atmosphere, friendly staff, and great coffee, it was a perfect place to relax."
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"instruction": "Generate a list of ingredients and instructions to make a dish.",
|
| 84 |
+
"input": "Apple Pie",
|
| 85 |
+
"output": "Ingredients:\n\u2022 2 cups all-purpose flour\n\u2022 1 teaspoon salt\n\u2022 1 cup (2 sticks) unsalted butter, cold\n\u2022 7 to 8 Granny Smith apples, peeled, cored, and thinly sliced \n\u2022 1 cup white sugar\n\u2022 1 tablespoon lemon juice\n\u2022 1/4 teaspoon nutmeg\n\u2022 2 tablespoons all-purpose flour\n\u2022 1 tablespoon cold butter\nInstructions:\n\u2022 Preheat the oven to 375 degrees F (190 degrees C).\n\u2022 In a medium bowl, mix together 2 cups flour and salt. Cut in 1 cup butter until mixture resembles coarse crumbs.\n\u2022 Place the mixture into a 9-inch pie dish.\n\u2022 In a large bowl, toss apples with sugar, lemon juice, nutmeg, and 2 tablespoons flour.\n\u2022 Spoon the apple mixture into the pastry-lined dish. Cut the 1 tablespoon butter into small pieces and sprinkle over the top.\n\u2022 Cover the filling with a top crust, and flute the edges. Cut several steam vents in the top.\n\u2022 Bake at 375 degrees F (190 degrees C) for 45 minutes, or until apples are tender and crust is golden brown."
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"instruction": "Develop an algorithm that can solve a Rubik's cube.",
|
| 89 |
+
"input": "",
|
| 90 |
+
"output": "The Rubik's cube can be solved using a combination of two algorithms - the \"Corner-Edge Pairing\" method, and the \"Layer-By-Layer\" method. \n\nThe Corner-Edge Pairing algorithm involves pairing up the incorrect cube pieces, and then orienting them correctly. This can be done by using a series of algorithms, involving \u201ctriple flips\u201d, \u201cuneven swap\u201d and \u201cloopen\u201d. \n\nThe Layer-By-Layer algorithm involves solving each layer of the cube independently. This algorithm utilizes the idea of creating a \u201ccross\u201d on the top layer, and then solving each corner and edge of the cube one-by-one. \n\nThese techniques can be combined to form an algorithm to solve a Rubik\u2019s cube."
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"instruction": "Given a sentence, rewrite it without changing the meaning.",
|
| 94 |
+
"input": "I had a great time at the party.",
|
| 95 |
+
"output": "I truly enjoyed the gathering."
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"instruction": "Money market account for emergency savings",
|
| 99 |
+
"input": "",
|
| 100 |
+
"output": "I'm not a fan of using cash for \"emergency\" savings. Put it in a stable investment that you can liquidate fairly quickly if you have to. I'd rather use credit cards for a while and then pay them off with investment funds if I must. Meanwhile those investments earn a lot more than the 0.1 percent savings or money market accounts will. Investment grade bond funds, for example, should get you a yield of between 4-6% right now. If you want to take a longer term view put that money into a stock index fund like QQQ or DIA. There is the risk it will go down significantly in a recession but over time the return is 10%. (Currently a lot more than that!) In any event you can liquidate securities and get the money into your bank is less than a week. If you leave it in cash it basically earns nothing while you wait for that rainy day which many never come."
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"instruction": "Rewrite an existing text to include vivid descriptions.",
|
| 104 |
+
"input": "Alice sat down in front of the computer and started typing",
|
| 105 |
+
"output": "Alice plopped down into her chair, her keys a gentle clacking against the old keyboard as she settled in for the task ahead. She gave a deep breath, centering herself for the typing marathon ahead of her, and with determined fingertips began to fill the blank screen with her thoughts."
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"instruction": "Provide a brief analysis of the given poem.",
|
| 109 |
+
"input": "\"Still I Rise\" by Maya Angelou\n\nYou may write me down in history\nWith your bitter, twisted lies,\nYou may trod me in the very dirt\nBut still, like dust, I'll rise.",
|
| 110 |
+
"output": "\"Still I Rise\" by Maya Angelou is a powerful poem that celebrates resilience and perseverance in the face of adversity. The poem speaks to the power of human spirit to overcome hardship and shatters the oppressive forces of bias and hate. In the first two lines, the speaker stands tall in the face of criticism and brutality and rises to challenge lies and injustice. The poem speaks of the strength of the human spirit and reminds readers that even though they may get knocked down, they will still rise."
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"instruction": "Do Affordable Care Act business requirements apply to \u201ccontrol groups?\u201d",
|
| 114 |
+
"input": "",
|
| 115 |
+
"output": "Yes, it applies to control groups. If I remember correctly common ownership rules are used to determine \"Applicable Large Employer\" status but if the time comes to owe a penalty, only the actual entity missing the mark will owe a penalty, not the entire control group. This is an excerpt from Section 4980H (the section that lays out employer requirements and penalties) (16) Employer. The term employer means the person that is the employer of an employee under the common-law standard. See \u00a7 31.3121(d)-1(c). For purposes of determining whether an employer is an applicable large employer, all persons treated as a single employer under section 414(b), (c), (m), or (o) are treated as a single employer. Thus, all employees of a controlled group of entities under section 414(b) or (c), an affiliated service group under section 414(m), or an entity in an arrangement described under section 414(o), are taken into account in determining whether the members of the controlled group or affiliated service group together are an applicable large employer. For purposes of determining applicable large employer status, the term employer also includes a predecessor employer (see paragraph (a)(36) of this section) and a successor employer. Link to the Federal Register"
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"instruction": "What is the current time in New York?",
|
| 119 |
+
"input": "",
|
| 120 |
+
"output": "The current time in New York is 1:38 PM EDT."
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"instruction": "Please describe the life of humans in 2050?",
|
| 124 |
+
"input": "",
|
| 125 |
+
"output": "In 2050, humans will live in a world where technology has shaped the way people live. There will be a greater reliance on automation and robotic assistance. The global economy will be powered by renewable energy sources, and most climate-disrupting emissions will have been drastically reduced. People will have improved access to healthcare, education, housing, and all types of opportunities. Cities will be more connected and cultural exchange will be a norm. Human quality of life will have vastly improved due to the advancements in technology."
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"instruction": "Generate a headline by combining two cliche expressions.",
|
| 129 |
+
"input": "",
|
| 130 |
+
"output": "\"A Picture Is Worth A Thousand Words\"."
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"instruction": "In what type of sentence is the word \"euphemism\" used?",
|
| 134 |
+
"input": "",
|
| 135 |
+
"output": "The word \"euphemism\" is typically used in a noun phrase. For example: \"He employed a euphemism when discussing the controversial subject.\""
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"instruction": "Name 5 job duties of a sales manager",
|
| 139 |
+
"input": "",
|
| 140 |
+
"output": "1. Identify new market opportunities and develop strategies to increase sales. \n2. Develop and Implement sales activities and plans to achieve company objectives.\n3. Monitor sales performance and address performance gaps.\n4. Oversee customer service and satisfaction.\n5. Recruit, hire and onboard sales personnel and other staff."
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"instruction": "Name five sports and the countries they originated in.",
|
| 144 |
+
"input": "",
|
| 145 |
+
"output": "1. Soccer (England) \n2. Basketball (United States) \n3. Baseball (United States) \n4. Hockey (Canada) \n5. Cricket (England)"
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"instruction": "Identify a major benefit of using machine learning.",
|
| 149 |
+
"input": "",
|
| 150 |
+
"output": "One of the major benefits of using machine learning is the ability to quickly understand and analyze large amounts of data. This can help companies identify patterns, trends, and relationships within the data that humans might not be able to find. This can lead to more efficient operations and higher successes within the organizations."
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"instruction": "\u201cBusiness day\u201d and \u201cdue date\u201d for bills",
|
| 154 |
+
"input": "",
|
| 155 |
+
"output": "You definitely have an argument for getting them to reverse the late fee, especially if it hasn't happened very often. (If you are late every month they may be less likely to forgive.) As for why this happens, it's not actually about business days, but instead it's based on when they know that you paid. In general, there are 2 ways for a company to mark a bill as paid: Late Fees: Some systems automatically assign late fees at the start of the day after the due date if money has not been received. In your case, if your bill was due on the 24th, the late fee was probably assessed at midnight of the 25th, and the payment arrived after that during the day of the 25th. You may have been able to initiate the payment on the company's website at 11:59pm on the 24th and not have received a late fee (or whatever their cutoff time is). Suggestion: as a rule of thumb, for utility bills whose due date and amount can vary slightly from month to month, you're usually better off setting up your payments on the company website to pull from your bank account, instead of setting up your bank account to push the payment to the company. This will ensure that you always get the bill paid on time and for the correct amount. If you still would rather push the payment from your bank account, then consider setting up the payment to arrive about 5 days early, to account for holidays and weekends."
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"instruction": "Provide an example of an ethical issue related to Artificial Intelligence technology.",
|
| 159 |
+
"input": "",
|
| 160 |
+
"output": "One ethical issue related to Artificial Intelligence technology is privacy. AI systems rely on large amounts of data to train and operate, which can include personal information of individuals. This data can easily be used for unintended purposes, such as targeting ads or making biased decisions. It is important for companies to ensure that their AI systems are able to protect the privacy of individuals, as well as adhere to applicable laws and regulations."
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"instruction": "Generate an example of an object-oriented design.",
|
| 164 |
+
"input": "",
|
| 165 |
+
"output": "In object-oriented design, a Car class could make use of a Wheel class which models individual car wheels. The Car class can use the Wheel class to model its wheels, allowing the Car class to keep track of the details involved with its wheels, such as what type of tire is being used and how much pressure is in each tire."
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"instruction": "I have savings and excess income. Is it time for me to find a financial advisor?",
|
| 169 |
+
"input": "",
|
| 170 |
+
"output": "Is my financial status OK? You have money for emergencies in the bank, you spend less than you earn. Yes, your status is okay. You will have a good standard of living if nothing changes from your status quo. How can I improve it? You are probably paying more in taxes than you would if you made a few changes. If you max out tax advantaged retirement accounts that would reduce the up-front taxes you are paying on your savings. Is now a right time for me to see a financial advisor? The best time to see a financial advisor is any time that your situation changes. New job? Getting married? Having a child? Got a big promotion or raise? Suddenly thinking about buying a house? Is it worth the money? How would she/he help me? If you pick an advisor who has incentive to help you rather than just pad his/her own pockets with commissions, then the advice is usually worth the money. If there is someone whose time is already paid for, that may be better. For example, if you get an accountant to help you with your taxes and ask him/her how to best reduce your taxes the next year, the advice is already paid-for in the fee you for the tax help. An advisor should help you minimize the high taxes you are almost certainly paying as a single earner, and minimize the stealth taxes you are paying in inflation (on that $100k sitting in the bank)."
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"instruction": "Combine the following two recipes to get a delicious meal.",
|
| 174 |
+
"input": "Recipe 1: 2 tablespoons olive oil, 1 onion chopped.\nRecipe 2: 4 cloves garlic, 1 red bell pepper diced.",
|
| 175 |
+
"output": "In a large skillet, heat the olive oil over medium heat. Add the onion and cook for 5 minutes, stirring often. Add the garlic and red bell pepper and cook for an additional 5 minutes, stirring often. Serve hot."
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"instruction": "Explain the role of B-DNA in the human body.",
|
| 179 |
+
"input": "",
|
| 180 |
+
"output": "B-DNA is the most common form of DNA, which is a double helix of two complementary DNA strands linked together. It is essential for a range of functions in the human body, including the replication, transcription and translation of genetic information. B-DNA is mainly responsible for the formation of proteins, which are the building blocks of many structures and organs in the body. It is also involved in the control of gene expression and metabolism to ensure the proper functioning of the body."
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"instruction": "How to learn about doing technical analysis? Any suggested programs or tools that teach it?",
|
| 184 |
+
"input": "",
|
| 185 |
+
"output": "A great way to learn is by watching then doing. I run a very successful technical analysis blog, and the first thing I like to tell my readers is to find a trader online who you can connect with, then watch them trade. I particularly like Adam Hewison, Marketclub.com - This is a great website, and they offer a great deal of eduction for free, in video format. They also offer further video based education through their ino.tv partner which is paid. Here is a link that has their free daily technical analysis based stock market update in video format. Marketclub Daily Stock Market Update Corey Rosenblum, blog.afraidtotrade.com - Corey is a Chartered Market Technician, and runs a fantastic technical analysis blog the focuses on market internals and short term trades. John Lansing, Trending123.com - John is highly successful trader who uses a reliable set of indicators and patterns, and has the most amazing knack for knowing which direction the markets are headed. Many of his members are large account day traders, and you can learn tons from them as well. They have a live daily chat room that is VERY busy. The other option is to get a mentor. Just about any successful trader will be willing to teach someone who is really interested, motivated, and has the time to learn. The next thing to do once you have chosen a route of education is to start virtual trading. There are many platforms available for this, just do some research on Google. You need to develop a trading plan and methodology for dealing with the emotions of trading. While there is no replacement for making real trades, getting some up front practice can help reduce your mistakes, teach you a better traders mindset, and help you with the discipline necessary to be a successful trader."
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"instruction": "Can my employer limit my maximum 401k contribution amount (below the IRS limit)?",
|
| 189 |
+
"input": "",
|
| 190 |
+
"output": "Companies are required BY THE IRS to try to get everybody to contribute minimal amounts to the 401K's. In the past, there were abuses and only the execs could contribute and the low paid workers were starving while the execs contributed huge amounts. On a year-by-year basis, if the low-paid employees don't contribute, the IRS punishes the high paid employees. Therefore, most employers provide a matching program to incentivize low-paid employees to contribute. This 9% limitation could happen in any year and it could have happened even before you got your pay raise, what matters is what the low-paid employees were doing at your company LAST YEAR."
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"instruction": "Generate an example to illustrate how sentiment analysis works.",
|
| 194 |
+
"input": "",
|
| 195 |
+
"output": "Sentiment analysis is a method of analyzing consumer opinions by assigning a numerical score to words and phrases in text data. For example, a sentiment analysis algorithm may assign a score of +1 for each occurrence of the word \"love\" and a score of -1 for each occurrence of the word \"hate\". By summing the scores for each word in a text, the algorithm can give an overall sentiment score, which will be positive if the overall opinion is positive and negative if the overall opinion is negative."
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"instruction": "Analyse the text and classify it into different genres",
|
| 199 |
+
"input": "He opened the window and looked out at the vast expanse of blue sky, framed by the gentle rolling hills",
|
| 200 |
+
"output": "Genre: Nature/Landscape description"
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"instruction": "Convert the date July 28th, 2020 to a Unix timestamp.",
|
| 204 |
+
"input": "",
|
| 205 |
+
"output": "July 28th, 2020 is equal to 1596126400 in Unix timestamp."
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"instruction": "Take a message and make it briefer while preserving the same meaning.",
|
| 209 |
+
"input": "I think it's important to check in on the project progress every two weeks.",
|
| 210 |
+
"output": "Check in on project progress every two weeks."
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"instruction": "Given a sentence, generate a paraphrase of the sentence that is 8 to 10 words in length.",
|
| 214 |
+
"input": "John quickly ran to the store.",
|
| 215 |
+
"output": "John hastened to the store."
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"instruction": "Who owned my shares before me?",
|
| 219 |
+
"input": "",
|
| 220 |
+
"output": "Shares do not themselves carry any identity. Official shareholders are kept at the registrar. In the UK, this may be kept up to date and publicly accessible. In the US, it is not, but this doesn't matter because most shares are held \"in street name\". For a fully detailed history, one would need access to all exchange records, brokerage records, and any trades transacted off exchange. These records are almost totally unavailable."
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"instruction": "What are the tax implications on selling the Share ISA portfolio?",
|
| 224 |
+
"input": "",
|
| 225 |
+
"output": "Assets held within an ISA or New ISA are completely free of CGT, so you won't have to pay either income tax or capital gains tax."
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"instruction": "In the following sentence, mention the type of phrase used.",
|
| 229 |
+
"input": "She sang a lullaby to the sleeping baby.",
|
| 230 |
+
"output": "The phrase used in the sentence is a gerund phrase: \"singing a lullaby\"."
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"instruction": "Summarize the story of 'Goldilocks and the Three Bears'.",
|
| 234 |
+
"input": "",
|
| 235 |
+
"output": "Goldilocks is a young girl who enters the home of the three bears. She tries out their food, chairs and beds, and eventually gets caught. The three bears are forgiving and she runs away."
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"instruction": "Company wants to sell all of its assets, worth more than share price?",
|
| 239 |
+
"input": "",
|
| 240 |
+
"output": "Why is the stock trading at only $5 per share? The share price is the perceived value of the company by people buying and selling the stock. Not the actual value of the company and all its assets. Generally if the company is not doing well, there is a perceived risk that it will burn out the money fast. There is a difference between its signed conditional sale and will get money and has got money. So in short, it's trading at $5 a share because the market doesn't feel like it's worth $12 per share. Quite a few believe there could be issues faced; i.e. it may not make the $12, or there will be additional obligations, i.e. employees may demand more layoff compensation, etc. or the distribution may take few years due to regulatory and legal hurdles. The only problem is the stock exchange states if the company has no core business, the stock will be suspended soon (hopefully they can release the $12 per share first). What will happen if I hold shares in the company, the stock gets suspended, and its sitting on $12 per share? Can it still distribute it out? Every country and stock markets have laid out procedures for de-listing a company and closing a company. The company can give $10 as say dividends and remaining later; or as part of the closure process, the company will distribute the balance among shareholders. This would be a long drawn process."
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"instruction": "Summarize what Isaac Newton discovered",
|
| 244 |
+
"input": "",
|
| 245 |
+
"output": "Sir Isaac Newton is widely regarded as one of the most influential scientists of all time. He discovered the laws of motion and gravity, developed calculus, and studied optics and physics. He also developed the mathematical methods needed to derive the laws of motion, studied the properties of white light and developed the first reflecting telescope. His discoveries laid the groundwork for many scientific advancements since then."
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"instruction": "Why would a company have 2 listings on the same exchange?",
|
| 249 |
+
"input": "",
|
| 250 |
+
"output": "A company can issue different kinds of shares. For example, some kinds of shares may get preference in dividends or payment in event of (company) bankruptcy. Preferred shares are an example of this. A company might have several kinds of preferred shares and a 'common stock'. Here is a good explanation. See too the Wikipedia article about preferred stock. Toronto-Dominion Bank (TD) is an example of a company that has fourteen different preferred share issues, each with its own listing on the Toronto Stock Exchange (TSE) and symbol. TD has one kind of common stock, which is also listed on the TSE. However, TD common equity trades much more actively than the preferred shares. Remember that preferred stock is a different security type than common stock e.g. common has voting rights, preferred does not."
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"instruction": "Report a bug for the given software.",
|
| 254 |
+
"input": "The newly released online shopping widget has been crashing intermittently.",
|
| 255 |
+
"output": "We have identified a bug on the new online shopping widget which causes unpredictable crashing."
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"instruction": "Write a short story which reflects the given scenario.",
|
| 259 |
+
"input": "A small-town girl is leaving her home to pursue her dream job in a big city.",
|
| 260 |
+
"output": "Mary was a small-town girl, with big city dreams. She was so inspired by the big city lifestyle she had seen in the movies and dreamed that one day she could experience it for herself.\n\nFinally the day arrived when she received the news that she had been offered her dream job in the big city. Excitedly, she began to pack her bags and told her family goodbye. \n\nAs she drove away, she looked back at the small town she had grown up in and smiled, feeling ready to take on the world. She thought of all the opportunities the big city would offer her and all of the people she would meet. It was a fresh start, and Mary could feel her heart pounding with anticipation as she drove off towards her future."
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"instruction": "Conduct a survey to find out what people think of the new iPhone.",
|
| 264 |
+
"input": "",
|
| 265 |
+
"output": "I conducted an online survey to find out what people think of the new iPhone. Out of the 500 people who participated in the survey, 80% said that they were extremely satisfied with the performance. 6% said that the phone was great but too expensive. The remaining 14% said that the phone had too many features and wasn't user-friendly."
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"instruction": "You are provided with a search query, find the top 5 relevant web pages to the query and output their URLs.",
|
| 269 |
+
"input": "Open source software",
|
| 270 |
+
"output": "https://opensource.com/, https://en.wikipedia.org/wiki/List_of_open-source_software, https://www.slant.co/topics/7625/~open-source-software, https://www.techopedia.com/definition/16575/open-source-software, https://en.wikipedia.org/wiki/Open-source_software."
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"instruction": "Given two lyrics, create a new melodic line that fits both.",
|
| 274 |
+
"input": "The sky is dark and wild\nIt's a long journey home",
|
| 275 |
+
"output": "And the wind's blowing free,\nWe'll take it mile by mile"
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"instruction": "Research and summarize the common practices for caring of rabbits.",
|
| 279 |
+
"input": "",
|
| 280 |
+
"output": "Caring for a rabbit involves providing them with a comfortable environment, ample space to roam, appropriate food and diet, protection from predators, and regular interaction. Rabbits are social creatures, so it is important to keep them in groups with other rabbits or to spend quality time with them. Their housing space should provide enough room for them to move around and have toys to play with. \n\nRabbits should be fed a proper diet of hay, fresh vegetables, and a small amount of timothy or rabbit pellets, to ensure they get the vitamins, minerals, and nutrients they need. Water should be available at all times and the food changed regularly. \n\nFinally, rabbits need to be protected from predators or other dangers, such as extreme heat or cold, as well as kept clean. Regularly grooming a rabbit can help keep their coat in good condition."
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"instruction": "Identify similar objects in the following list.",
|
| 284 |
+
"input": "Banana, Peach, Carrot, Apple",
|
| 285 |
+
"output": "Banana and Apple are similar objects as they are both fruits."
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"instruction": "Delete the adjective 'bustling' from the following sentence and rewrite it.",
|
| 289 |
+
"input": "The bustling city of Tokyo was awe-inspiring.",
|
| 290 |
+
"output": "The city of Tokyo was awe-inspiring."
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"instruction": "Fill in the blank with an adjective that describes the following: A person who enjoys learning new things.",
|
| 294 |
+
"input": "",
|
| 295 |
+
"output": "A person who enjoys learning new things is inquisitive."
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"instruction": "Write an IF-THEN statement to control the temperature in a room.",
|
| 299 |
+
"input": "",
|
| 300 |
+
"output": "If the temperature in the room is above 75 degrees, then turn on the air conditioner."
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"instruction": "Generate a suggested next move if a checker piece is in position 7C.",
|
| 304 |
+
"input": "Position = 7C",
|
| 305 |
+
"output": "If the checker piece is in position 7C, the suggested next move would be to move the checker diagonally to position 8B. This move would capture an opponent's piece if there is one in position 8B. If there is not an opponent's piece in that position, the checker piece can remain in 7C."
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"instruction": "Can I get a discount on merchandise by paying with cash instead of credit?",
|
| 309 |
+
"input": "",
|
| 310 |
+
"output": "Slightly off topic... Not merchandise, but I paid for various doctor's appointments with cash (as opposed to paying with health insurance). I'd call ahead of time and notify them that I'd be paying in cash. I got ridiculous discounts, sometimes even less than the copay. I do not know why this discrepancy exists and I didn't want to ask for fear of messing up a good thing."
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"instruction": "Summarize the main reasons why small businesses need a website.",
|
| 314 |
+
"input": "",
|
| 315 |
+
"output": "Having a website is essential for small businesses because it allows them to market their products and services, to reach new customers, to generate leads and sales, and to increase their visibility. It also makes it easier for customers to find information about their business, providing them a platform to build trust and credibility."
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"instruction": "Compose a tweet of no more than 280 characters to advertise a new gadget.",
|
| 319 |
+
"input": "",
|
| 320 |
+
"output": "Introducing the new SuperGadget! With its sleek design and automated features, this is the perfect gadget to make your life easier. #SuperGadget #Technology #Convenience"
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"instruction": "Find five sources to back up the statement",
|
| 324 |
+
"input": "Humans are the primary cause of global climate change.",
|
| 325 |
+
"output": "1. Driver, N. (2019). Humans are the primary cause of global climate change, says NASA. [online] The Guardian. Available at: https://www.theguardian.com/environment/2019/aug/07/humans-are-the-primary-cause-of-global-climate-change-says-nasa [Accessed 14 Apr. 2020].\n \n2. IPCC, (2018). Summary for policy makers. In: Global Warming of 1.5\u00b0C. An IPCC Special Report. World Meteorological Organisation, pp.6. \n\n3. Cook, J. et al. (2013). Quantifying the consensus on anthropogenic global warming in the scientific literature. Environmental Research Letters, 8(2), p.020,775. \n \n4. Anderson, K. et al. (2008). Anthropogenic contributions and natural trends in global and regional temperature change. Annual Review of Environment and Resources, 33(1), pp.119\u2013139.\n\n5. Hansen, J. et al. (2012). Perceptions of climate change: The new climate dice. Proceedings of the National Academy of Sciences, 112(1), p.513\u2013516."
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"instruction": "Generate three investment options for someone who has 10,000 dollars and wants to invest it for 10 years.",
|
| 329 |
+
"input": "",
|
| 330 |
+
"output": "1. Mutual funds - Invest in low-cost, diversified mutual funds which can help reduce risk.\n2. Exchange-traded funds (ETFs) - A convenient way to invest in a variety of different markets.\n3. Bond funds - Invest in safe and secure bonds to earn regular income."
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"instruction": "Find a definition for the term 'big O notation'",
|
| 334 |
+
"input": "",
|
| 335 |
+
"output": "Big O notation is a mathematical notation that describes the limitation of an algorithm in relation to the input size. It measures the time complexity of an algorithm, i.e. the amount of time taken to execute it, and the space complexity, i.e. the amount of memory required. It is often used to compare different algorithms to determine which one is more efficient in terms of time and space."
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"instruction": "Name five famous Spanish restaurants in Madrid.",
|
| 339 |
+
"input": "",
|
| 340 |
+
"output": "Casa Buenavista, Lateral Gastrobar, El Brillante, Restaurante Bot\u00edn, and Taberna La Bola are some of the most famous Spanish restaurants in Madrid."
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"instruction": "What to sell when your financial needs change, stocks or bonds?",
|
| 344 |
+
"input": "",
|
| 345 |
+
"output": "So I don't have any problems with your analysis or the comments associated with it. I just wanted to mention that no one is talking about taxes. Your answer....Figure out new portfolio breakdown and sell to 1.) Get money I need and 2.) re-balance the portfolio to my new target allocations is completely correct. (Unimpeachable in my opinion.) However, when you calculate what you need to sell to meet your current cash needs make sure to include in that analysis money to pay taxes on anything you sell for a gain, or keep some invested to account for the tax money you would save by selling things for a loss. The actual mechanics of calculating what these amounts are are fairly involved but not difficult to understand. (IE every situation is different.) Best of luck to you, and I hope your cashflow gets back up to its previous level soon."
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"instruction": "Brainstorm three use cases for a certain technology.",
|
| 349 |
+
"input": "Augmented Reality",
|
| 350 |
+
"output": "1. Augmented Reality can be used in medicine, allowing real-time 3D visualizations of medical scans and data, providing more accurate diagnoses and more effective treatments. \n2. Augmented Reality can be used in gaming and entertainment, providing a more immersive experience. \n3. Augmented Reality can be used in architecture, allowing architects to visualize a proposed structure as if it were already built."
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"instruction": "Suggest the most helpful study habits for a student preparing for an upcoming English Literature exam.",
|
| 354 |
+
"input": "",
|
| 355 |
+
"output": "The best study habits for a student preparing for an upcoming English Literature exam include reading the assigned literature and class notes thoroughly, outlining key points and ideas in the literature and taking part in study groups or one-on-one discussions with classmates. Additionally, it is important to take frequent practice exams to help identify areas of weakness. Other helpful habits include breaking the material into smaller chunks for more manageable study sessions and using mnemonic devices to help memorize numbers, dates, and facts."
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"instruction": "Why would you ever turn down a raise in salary?",
|
| 359 |
+
"input": "",
|
| 360 |
+
"output": "If you have children in a university institution, then your annual salary is reported via financial aid forms. The small raise could be the difference between full tuition covered and only half tuition covered."
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"instruction": "Why does the biotechnology industry have such a high PE ratio?",
|
| 364 |
+
"input": "",
|
| 365 |
+
"output": "Residential Construction at 362x, by the way. I'm going to hazard a guess here - Say XYZ corp trades at $100, and it's showing a normal earnings of $10 the last few years. Its industry falls on hard times, and while it makes enough to keep its doors open, profits fall to $1. The company itself is still sound, but the small earnings result in a high P/E. By the way, its book value is $110, and they have huge cash on the books along with real estate. I offer these details to show why the price doesn't drop like a rock. Now, biotech may be in a period of low reported earnings but with future results expected to justify the price. On one hand it may be an anomaly, with earnings due to rise, or it may be a bit of a bubble. An analyst for this sector should be able to comment if I'm on the right track."
|
| 366 |
+
},
|
| 367 |
+
{
|
| 368 |
+
"instruction": "Should I pay off my 401k loan or reinvest the funds elsewhere?",
|
| 369 |
+
"input": "",
|
| 370 |
+
"output": "This summer I used a loan from my 401(k) to help pay for the down payment of a new house. We planned on selling a Condo a few months later, so we only needed the loan for a short period but wanted to keep monthly payments low since we would be paying two mortgages for a few months. I also felt like the market might take a dip in the future, so I liked the idea of trying to cash out high and buy back low (spoiler alert: this didn't happen). So in July 2017 I withdrew $17,000 from my account (Technically $16,850.00 principal and $150 processing fee) at an effective 4.19% APR (4% rate and then the fee), with 240 scheduled payments of $86.00 (2 per month for 10 years). Over the lifetime of the loan the total finance charge was $3,790, but that money would be paid back into my account. I was happy with the terms, and it helped tide things over until the condo was sold a few months later. But then I decided to change jobs, and ended up having to pay back the loan ~20 weeks after it was issued (using the proceeds from the sale of the condo). During this time the market had done well, so when I paid back the funds the net difference in shares that I now owned (including shares purchased with the interest payments) was $538.25 less than today's value of the original count of shares that were sold to fund the loan. Combined with the $150 fee, the overall \"cost\" of the 20 week loan was about 4.05%. That isn't the interest rate (interest was paid back to my account balance), but the value lost due to the principal having been withdrawn. On paper, my account would be worth that much more if I hadn't withdrawn the money. Now if you extrapolate the current market return into 52 weeks, you can think of that loan having an APR \"cost\" of around 10.5% (Probably not valid for a multi year calculation, but seems accurate for a 12 month projection). Again, that is not interest paid back to the account, but instead the value lost due to the money not being in the account. Sure, the market could take a dip and I may be able to buy the shares back at a reduced cost, but that would require keeping sizable liquid assets around and trying to time the market. It also is not something you can really schedule very well, as the loan took 6 days to fund (not including another week of clarifying questions back/forth before that) and 10 day to repay (from the time I initiated the paperwork to when the check was cashed and shares repurchased). So in my experience, the true cost of the loan greatly depends on how the market does, and if you have the ability to pay back the loan it probably is worth doing so. Especially since you may be forced to do so at any time if you change jobs or your employment is terminated."
|
| 371 |
+
},
|
| 372 |
+
{
|
| 373 |
+
"instruction": "What are the steps to implement a convolutional neural network?",
|
| 374 |
+
"input": "",
|
| 375 |
+
"output": "Implementing a convolutional neural network (CNN) involves several steps. First, the data is pre-processed, which includes normalizing and reshaping it into the right format. Next, the network architecture is constructed, which includes selecting the right layers, defining the number of filters and the size of the convolutional window. Following that, the weights of the model are optimized by backpropagation within the training process. Once the model has been trained, it can be used to make accurate predictions. Finally, the model can be tested to ensure that the accuracy is satisfactory."
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"instruction": "Write two statements about how the development of technology changed the world.",
|
| 379 |
+
"input": "",
|
| 380 |
+
"output": "The development of technology has changed the world significantly. It has enabled us to communicate and interact with others from all over the world instantaneously, and it has opened up new opportunities for individuals to access information quickly and conveniently. Furthermore, technology has made many areas of work more efficient, providing us with higher productivity and more specialized skills."
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"instruction": "Profiting from the PWC Money Tree",
|
| 384 |
+
"input": "",
|
| 385 |
+
"output": "The hardest part seems to be knowing exactly when to sell the stock. Well yes, that's the problem with all stock investing. Reports come out all the time, sometimes even from very smart people with no motivation to lie, about expected earnings for this company, or for that industry. Whether those predictions come true is something you will only find out with time. What you are considering is using financial information available to you (and equally available to the public) to make investment choices. This is called 'fundamental analysis'; that is, the analysis of the fundamentals of a business and what it should be worth. It forms the basis of how many investment firms decide where to put their money. In a perfectly 'efficient' market, all information available to the public is immediately factored into the market price for that company's stock. ie: if a bank report states with absolute certainty (through leaked documents) that Coca-Cola is going to announce 10% revenue growth tomorrow, then everyone will immediately buy Coca-Cola stock today, and then tomorrow there would be no impact. Even if PwC is 100% accurate in its predictions, if the rest of the market agrees with them, then the price at the time of IPO would equal the future value of the cashflows, meaning there would be no gain unless results surpassed expectations. So what you are proposing is to take one sliver of the information available to the public (have you also read all publicly available reports on those businesses and their industries?), and using that to make a high risk investment. Are you going to do better than the investment firms that have teams of researchers and years of experience in the investment world? You can do quite well by picking individual stocks, but you can also lose a lot of money if you do it haphazardly. Be aware that there is risk in doing any type of investing. There is higher than average risk if you invest in equities ('the stock market'). There is higher risk still, if you pick individual stocks. There is yet even higher risk, if you pick small startup companies. There are some specific interesting side-elements with your proposal to purchase stock about to have an IPO - those are better dealt with in a separate question if you want more information; search this site for 'IPO' and you should find a good starting point. In short, the company about to go public will hire a firm of analysts who will try to calculate the best price the public will accept for an offering of shares. Stock often goes up after IPO, but not always. Sometimes the company doesn't even fill its full IPO order, adding a new type of risk to a potential investor, that the stock will drop on day 1. Consider an analogy outside the investing world: Let's say Auto Trader magazine prints an article that says \"all 2015 Honda Civics are worth $15,000 if they have less than 50,000 Miles.\" Assume you have no particular knowledge about cars. If you read this article, and you see an ad in the paper the next day for a Honda Civic with 40k miles, should you buy it for $14k? The answer is not without more research. And even if you determine enough about cars to find one for $14k that you can reasonably sell for $15k, there's a whole world of mechanics out there who buy and sell cars for a living, and they have an edge both because they can repair the cars themselves to sell for more, and also because they have experience to spot low-offers faster than you. And if you pick a clunker (or a stock that doesn't perform even when everyone expected it would), then you could lose some serious money. As with buying and selling individual stocks, there is money to be made from car trading, but that money gets made by people who really know what they're doing. People who go in without full information are the ones who lose money in the long run."
|
| 386 |
+
},
|
| 387 |
+
{
|
| 388 |
+
"instruction": "Evaluate the following phrase by transforming it into the spelling given.",
|
| 389 |
+
"input": "resipt --> receipt",
|
| 390 |
+
"output": "Correct. The spelling of the phrase \"resipt\" was transformed into \"receipt\"."
|
| 391 |
+
},
|
| 392 |
+
{
|
| 393 |
+
"instruction": "Name three effects of deforestation.",
|
| 394 |
+
"input": "",
|
| 395 |
+
"output": "Three effects of deforestation include loss of habitat for wildlife, disruption of water cycles and climate change. Deforestation leads to a loss of habitats for animals, making it more difficult for them to find food and shelter. Deforestation also disrupts the natural water cycle by preventing absorption of water by trees, leading to drier soils, floods, mudslides, and droughts. Finally, deforestation contributes to climate change due to the release of carbon dioxide, a major greenhouse gas."
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"instruction": "Write a five-sentence passage about a particular character.",
|
| 399 |
+
"input": "",
|
| 400 |
+
"output": "Harry was a young man who lived in a small town. He was a kind-hearted soul but had a hard time making friends due to his shyness. Despite this, Harry had a dream of becoming a famous singer, and he was passionate about his dream. He practiced singing every day, confident that one day he would make it big. And one day, he finally got his chance to show off his talents when he was offered to perform in a local talent show."
|
| 401 |
+
},
|
| 402 |
+
{
|
| 403 |
+
"instruction": "Recite an optimistic proverb.",
|
| 404 |
+
"input": "",
|
| 405 |
+
"output": "Where there is a will, there is a way."
|
| 406 |
+
},
|
| 407 |
+
{
|
| 408 |
+
"instruction": "Design a photographic project to document the culture of a specific city.",
|
| 409 |
+
"input": "City: Melbourne",
|
| 410 |
+
"output": "For my photographic project, I would document the culture of Melbourne. I would use my photographs to capture the city's unique mix of suburban, alternative, and traditional cultures. I would take pictures of the people and places in the city, such as street art, cafes, libraries, and theatres. I would also look to capture the moments of everyday life, like people enjoying a drink in a beer garden or walking through the park on a sunny day. This project would demonstrate the multiculturalism of Melbourne and provide an insight into its vibrant culture."
|
| 411 |
+
},
|
| 412 |
+
{
|
| 413 |
+
"instruction": "Find any mistakes in the following sentence and give me the corrected sentence.",
|
| 414 |
+
"input": "He had to work really hardly to get the job.",
|
| 415 |
+
"output": "He had to work really hard to get the job."
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"instruction": "Rearrange this sentence to make it sound more natural: \"It's impossible for me to do this\"",
|
| 419 |
+
"input": "",
|
| 420 |
+
"output": "I can't do this, it's impossible."
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"instruction": "Apartment lease renewal - is this rate increase normal?",
|
| 424 |
+
"input": "",
|
| 425 |
+
"output": "Yes, automatic rate increases are typical in my experience (and I think it's very greedy, when it's based on nothing except that your lease is up for renewal, which is the situation you are describing). Yes, you should negotiate. I've had success going to the apartment manager and having this conversation: Make these points: Conclude: I am not open to a rate increase, though I will sign a renewal at the same rate I am paying now. This conversation makes me very uncomfortable, but I try not to show it. I was able to negotiate a lease renewal at the same rate this way (in a large complex in Sacramento, CA). If you are talking to a manager and not an owner, they will probably have to delay responding until they can check with the owner. The key really is that they want to keep units rented, especially when units are staying empty. Empty units are lost income for the owner. It is the other empty units that are staying empty that are the huge point in your favor."
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"instruction": "Is it OK to use a credit card on zero-interest to pay some other credit cards with higher-interest?",
|
| 429 |
+
"input": "",
|
| 430 |
+
"output": "I am sure everyone is different, but it has helped me a great deal. I have had several card balances go up and the interest on those per month was more than $200 in just interest combined. I transferred the balances over to 0% for 15 months \u2013 with a fee, so the upfront cost was about $300. However, over the next 15 months at 0% I'm saving over $200 each month. Now I have the money to pay everything off at 14 months. I will not be paying any interest after that, and I cut up all of my cards so I won't rack up the bills with interest on them anymore. Now, if I can't buy it with a debit card or cash, I don't get it. My cards went up so high after remodeling a home so they were justified. It wasn't because I didn't pay attention to what I could afford. My brother, on the other hand, has trouble using credit cards properly and this doesn't work for him."
|
| 431 |
+
},
|
| 432 |
+
{
|
| 433 |
+
"instruction": "How can banks afford to offer credit card rewards?",
|
| 434 |
+
"input": "",
|
| 435 |
+
"output": "There are 3 entities in a credit card transaction; Typically when you swipe for 100, the merchant only gets around 97.5. The 2.5 is divided amongst the 3 entities, roughly around 0.5 for the Merchant Bank, around 0.5 for the Card Network and a lions share to Issuing Bank of around 1.5 The reason Issuing Bank gets large share is because they take the risk and provide the credit to customer. Typically the Issuing Bank would pay the Merchant bank via the Card Network the money in couple of days. So the Merchant Bank is not out of funds. The Issuing Bank on the other hand would have given you a credit of say 10 to 50 days depending on when you made the transaction and when the payment is due. On an average 30 days of credit. So roughly the Acquiring Bank is lending money at the rate of 18%. It is from this money the Issuing Bank would give out rewards, which is typically less than 1%. Also in cases where say Merchant Bank and the Issuing Bank are same, Bank would make money on both the legs of transaction and hence launch co-branded cards with better rewards. The above numbers are illustrative and actual practices vary from Bank to Bank to card Network to Country Related question at How do credit card companies make profit?"
|
| 436 |
+
},
|
| 437 |
+
{
|
| 438 |
+
"instruction": "What steps are required to transfer real estate into a LLC?",
|
| 439 |
+
"input": "",
|
| 440 |
+
"output": "especially considering it has a mortgage on it (technically a home equity loan on my primary residence). I'm not following. Does it have a mortgage on it, or your primary residence (a different property) was used as a security for the loan? If it is HELOC from a different property - then it is really your business what to do with it. You can spend it all on casinos in Vegas for all that the bank cares. Is this a complicated transaction? Any gotchas I should be aware of before embarking on it? Obviously you should talk to an attorney and a tax adviser. But here's my two cents: Don't fall for the \"incorporate in Nevada/Delaware/Wyoming/Some other lie\" trap. You must register in the State where you live, and in the State where the property is. Incorporating in any other State will just add complexity and costs, and will not save you anything whatsoever. 2.1 State Taxes - some States tax LLCs. For example, in California you'll pay at least $800 a year just for the right of doing business. If you live in California or the property is in California - you will pay this if you decide to set up an LLC. 2.2 Income taxes - make sure to not elect to tax your LLC as a corporation. The default for LLC is \"disregarded\" status and it will be taxed for income tax purposes as your person. I.e.: IRS doesn't care and doesn't know about it (and most States, as well). If you actively select to tax it as a corporation (there's such an option) - it will cost you very dearly. So don't, and if someone suggest such a thing to you - run away from that person as fast as you can. Mortgages - it is very hard to get a mortgage when the property is under the LLC. If you already have a mortgage on that property (the property is the one securing the loan) - it may get called once you transfer it into LLC, since from bank's perspective that would be transferring ownership. Local taxes - transferring into LLC may trigger a new tax assessment. If you just bought the property - that will probably not matter much. If it appreciated - you may get hit with higher property taxes. There are also many little things - once you're a LLC and not individual you'll have to open a business bank account, will probably need a new insurance policy, etc etc. These don't add much to costs and are more of an occasional nuisance."
|
| 441 |
+
},
|
| 442 |
+
{
|
| 443 |
+
"instruction": "Describe a possible career path for someone interested in Machine Learning.",
|
| 444 |
+
"input": "",
|
| 445 |
+
"output": "Someone interested in Machine Learning could pursue a career path in Data Science. A Data Scientist specializing in Machine Learning would work with companies and organizations to develop algorithms that help to analyze data. This can include tasks like developing predictive models, developing recommendation systems, and creating automations. After mastering the basics of Machine Learning, this data scientist could go on to specialize in areas like deep learning or computer vision. Once they have experience in the field, they could then move on to more advanced roles such as leading or managing Machine Learning teams."
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"instruction": "Which English word is the most common?",
|
| 449 |
+
"input": "",
|
| 450 |
+
"output": "The most common English word is \"the\"."
|
| 451 |
+
},
|
| 452 |
+
{
|
| 453 |
+
"instruction": "Provide an example of an exception handling program in Java",
|
| 454 |
+
"input": "",
|
| 455 |
+
"output": "public class ExceptionHandlingExample {\n\tpublic static void main(String[] args) {\n\t\ttry {\n\t\t\tint num1 = 30;\n\t\t\tint num2 = 0;\n\t\t\tint result = num1/num2;\n\t\t\tSystem.out.println(result);\n\t\t} \n\t\t\n\t\tcatch (ArithmeticException e) {\n\t\t\tSystem.out.println(\"Division cannot be done by 0\");\n\t\t\te.printStackTrace();\n\t\t}\n\t}\n}"
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"instruction": "How can I conserve water at home?",
|
| 459 |
+
"input": "",
|
| 460 |
+
"output": "There are several ways to conserve water at home. One is to repair any dripping taps and leaking pipes as soon as possible. Another is to use water-efficient appliances such as washing machines and dishwashers. Additionally, placing a bucket in the bathtub while you are showering can help you save up to 20 gallons of water. Finally, collecting water from air conditioners and refrigerators and using that water to water plants can also be beneficial."
|
| 461 |
+
},
|
| 462 |
+
{
|
| 463 |
+
"instruction": "Give three examples of using technology for efficiency.",
|
| 464 |
+
"input": "",
|
| 465 |
+
"output": "1. Automating processes such as email marketing and customer service tasks.\n2. Using data analytics to make informed decisions about the business and its operations.\n3. Utilizing cloud-based technologies for remote work, collaboration, and communication."
|
| 466 |
+
},
|
| 467 |
+
{
|
| 468 |
+
"instruction": "Create a web page with a photo carousel and an introduction",
|
| 469 |
+
"input": "Photos: person 1, person 2, person 3 \nIntro: At ABC Company, our team strives for excellence",
|
| 470 |
+
"output": "I created a web page with a photo carousel featuring pictures of Person 1, Person 2, and Person 3. Underneath the carousel is an introduction to ABC Company and its team. The introduction reads: \"At ABC Company, our team strives for excellence. Our goal is to exceed customer expectations and meet the highest standards of quality and service. We take pride in our work and strive to keep our customers satisfied.\""
|
| 471 |
+
},
|
| 472 |
+
{
|
| 473 |
+
"instruction": "What is the procedure for creating an effective PowerPoint presentation?",
|
| 474 |
+
"input": "",
|
| 475 |
+
"output": "Creating an effective PowerPoint presentation involves a few simple steps. Begin by deciding on the purpose and goal of the presentation. This will help determine the structure and content of the presentation. Then, create content that supports the purpose and goal. Content should be clear and concise, and visuals such as charts, graphs, and images should be used to illustrate key points. Keep text to a minimum and make sure it is in an easy-to-read font. Finally, practice the presentation to ensure that the timing is appropriate and that the presentation flows smoothly."
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"instruction": "What is market capitalization? [duplicate]",
|
| 479 |
+
"input": "",
|
| 480 |
+
"output": "Market Capitalization is the product of the current share price (the last time someone sold a share of the stock, how much?) times the number of outstanding shares of stock, summed up over all of the stock categories. Assuming the efficient market hypothesis and a liquid market, this gives the current total value of the companies' assets (both tangible and intangible). Both the EMH and perfect liquidity may not hold at all times. Beyond those theoretical problems, in practice, someone trying to buy or sell the company at that price is going to be in for a surprise; the fact that someone wants to sell that many stocks, or buy that many stocks, will move the price of the company stock."
|
| 481 |
+
},
|
| 482 |
+
{
|
| 483 |
+
"instruction": "Compose a tweet-length review for a movie you recently watched.",
|
| 484 |
+
"input": "",
|
| 485 |
+
"output": "Just watched Spider-Man: Far From Home and it was fantastic! Action-packed with plenty of heart-pumping moments - definitely an exciting way to end the latest Spider-Man trilogy! #SpiderManFFH #Spiderman #Marvel"
|
| 486 |
+
}
|
| 487 |
+
]
|
data_aggregation/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data Aggregation Module
|
| 3 |
+
|
| 4 |
+
This module handles data collection, preprocessing, and dataset creation
|
| 5 |
+
for model training. Includes synthetic data generation, quality validation,
|
| 6 |
+
and tool use training data generation.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from .collectors import DataCollector, TextDataCollector, JSONDataCollector
|
| 10 |
+
from .preprocessors import TextPreprocessor, DataCleaner
|
| 11 |
+
from .dataset_builder import DatasetBuilder
|
| 12 |
+
from .synthetic_generator import SyntheticDataGenerator
|
| 13 |
+
from .quality_validator import QualityValidator
|
| 14 |
+
from .hf_dataset_loader import HuggingFaceDatasetLoader
|
| 15 |
+
from .tool_use_generator import ToolUseGenerator
|
| 16 |
+
|
| 17 |
+
__all__ = [
|
| 18 |
+
'DataCollector',
|
| 19 |
+
'TextDataCollector',
|
| 20 |
+
'JSONDataCollector',
|
| 21 |
+
'TextPreprocessor',
|
| 22 |
+
'DataCleaner',
|
| 23 |
+
'DatasetBuilder',
|
| 24 |
+
'SyntheticDataGenerator',
|
| 25 |
+
'QualityValidator',
|
| 26 |
+
'HuggingFaceDatasetLoader',
|
| 27 |
+
'ToolUseGenerator'
|
| 28 |
+
]
|
data_aggregation/collectors.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data Collectors Module
|
| 3 |
+
|
| 4 |
+
Provides various data collection methods for training data.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from typing import List, Dict, Any, Optional
|
| 10 |
+
from abc import ABC, abstractmethod
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class DataCollector(ABC):
|
| 14 |
+
"""Base class for data collectors."""
|
| 15 |
+
|
| 16 |
+
@abstractmethod
|
| 17 |
+
def collect(self) -> List[Dict[str, Any]]:
|
| 18 |
+
"""
|
| 19 |
+
Collect data from source.
|
| 20 |
+
|
| 21 |
+
Returns:
|
| 22 |
+
List of data examples
|
| 23 |
+
"""
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class TextDataCollector(DataCollector):
|
| 28 |
+
"""Collect data from text files."""
|
| 29 |
+
|
| 30 |
+
def __init__(self, file_path: str):
|
| 31 |
+
"""
|
| 32 |
+
Initialize text data collector.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
file_path: Path to text file
|
| 36 |
+
"""
|
| 37 |
+
self.file_path = Path(file_path)
|
| 38 |
+
|
| 39 |
+
def collect(self) -> List[Dict[str, Any]]:
|
| 40 |
+
"""
|
| 41 |
+
Collect data from text file.
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
List of text examples
|
| 45 |
+
"""
|
| 46 |
+
if not self.file_path.exists():
|
| 47 |
+
raise FileNotFoundError(f"File not found: {self.file_path}")
|
| 48 |
+
|
| 49 |
+
with open(self.file_path, 'r', encoding='utf-8') as f:
|
| 50 |
+
lines = f.readlines()
|
| 51 |
+
|
| 52 |
+
# Simple line-by-line collection
|
| 53 |
+
data = []
|
| 54 |
+
for i, line in enumerate(lines):
|
| 55 |
+
line = line.strip()
|
| 56 |
+
if line:
|
| 57 |
+
data.append({
|
| 58 |
+
"id": i,
|
| 59 |
+
"text": line,
|
| 60 |
+
"source": str(self.file_path)
|
| 61 |
+
})
|
| 62 |
+
|
| 63 |
+
return data
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class JSONDataCollector(DataCollector):
|
| 67 |
+
"""Collect data from JSON files."""
|
| 68 |
+
|
| 69 |
+
def __init__(self, file_path: str):
|
| 70 |
+
"""
|
| 71 |
+
Initialize JSON data collector.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
file_path: Path to JSON file
|
| 75 |
+
"""
|
| 76 |
+
self.file_path = Path(file_path)
|
| 77 |
+
|
| 78 |
+
def collect(self) -> List[Dict[str, Any]]:
|
| 79 |
+
"""
|
| 80 |
+
Collect data from JSON file.
|
| 81 |
+
|
| 82 |
+
Expects format:
|
| 83 |
+
[
|
| 84 |
+
{"instruction": "...", "input": "...", "output": "..."},
|
| 85 |
+
...
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
List of data examples
|
| 90 |
+
"""
|
| 91 |
+
if not self.file_path.exists():
|
| 92 |
+
raise FileNotFoundError(f"File not found: {self.file_path}")
|
| 93 |
+
|
| 94 |
+
with open(self.file_path, 'r', encoding='utf-8') as f:
|
| 95 |
+
data = json.load(f)
|
| 96 |
+
|
| 97 |
+
# Ensure data is a list
|
| 98 |
+
if isinstance(data, dict):
|
| 99 |
+
# If it's a dict, try to extract data from common keys
|
| 100 |
+
if "data" in data:
|
| 101 |
+
data = data["data"]
|
| 102 |
+
elif "examples" in data:
|
| 103 |
+
data = data["examples"]
|
| 104 |
+
else:
|
| 105 |
+
# Wrap single example in list
|
| 106 |
+
data = [data]
|
| 107 |
+
|
| 108 |
+
return data
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class APIDataCollector(DataCollector):
|
| 112 |
+
"""Collect data from API endpoints."""
|
| 113 |
+
|
| 114 |
+
def __init__(self, api_url: str, headers: Optional[Dict] = None):
|
| 115 |
+
"""
|
| 116 |
+
Initialize API data collector.
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
api_url: API endpoint URL
|
| 120 |
+
headers: Optional HTTP headers
|
| 121 |
+
"""
|
| 122 |
+
self.api_url = api_url
|
| 123 |
+
self.headers = headers or {}
|
| 124 |
+
|
| 125 |
+
def collect(self) -> List[Dict[str, Any]]:
|
| 126 |
+
"""
|
| 127 |
+
Collect data from API.
|
| 128 |
+
|
| 129 |
+
Returns:
|
| 130 |
+
List of data examples
|
| 131 |
+
"""
|
| 132 |
+
import requests
|
| 133 |
+
|
| 134 |
+
response = requests.get(self.api_url, headers=self.headers)
|
| 135 |
+
response.raise_for_status()
|
| 136 |
+
|
| 137 |
+
data = response.json()
|
| 138 |
+
|
| 139 |
+
# Handle different response formats
|
| 140 |
+
if isinstance(data, list):
|
| 141 |
+
return data
|
| 142 |
+
elif isinstance(data, dict):
|
| 143 |
+
# Try common keys
|
| 144 |
+
for key in ["data", "results", "items", "examples"]:
|
| 145 |
+
if key in data:
|
| 146 |
+
return data[key]
|
| 147 |
+
# Otherwise wrap in list
|
| 148 |
+
return [data]
|
| 149 |
+
|
| 150 |
+
return []
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
class CSVDataCollector(DataCollector):
|
| 154 |
+
"""Collect data from CSV files."""
|
| 155 |
+
|
| 156 |
+
def __init__(self, file_path: str):
|
| 157 |
+
"""
|
| 158 |
+
Initialize CSV data collector.
|
| 159 |
+
|
| 160 |
+
Args:
|
| 161 |
+
file_path: Path to CSV file
|
| 162 |
+
"""
|
| 163 |
+
self.file_path = Path(file_path)
|
| 164 |
+
|
| 165 |
+
def collect(self) -> List[Dict[str, Any]]:
|
| 166 |
+
"""
|
| 167 |
+
Collect data from CSV file.
|
| 168 |
+
|
| 169 |
+
Returns:
|
| 170 |
+
List of data examples (dict per row)
|
| 171 |
+
"""
|
| 172 |
+
import csv
|
| 173 |
+
|
| 174 |
+
if not self.file_path.exists():
|
| 175 |
+
raise FileNotFoundError(f"File not found: {self.file_path}")
|
| 176 |
+
|
| 177 |
+
data = []
|
| 178 |
+
with open(self.file_path, 'r', encoding='utf-8') as f:
|
| 179 |
+
reader = csv.DictReader(f)
|
| 180 |
+
for row in reader:
|
| 181 |
+
data.append(dict(row))
|
| 182 |
+
|
| 183 |
+
return data
|
data_aggregation/dataset_builder.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Dataset Builder Module
|
| 3 |
+
|
| 4 |
+
Handles train/test splitting and dataset creation.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import random
|
| 8 |
+
from typing import List, Dict, Any, Tuple, Optional
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class DatasetBuilder:
|
| 12 |
+
"""Build and split datasets for training."""
|
| 13 |
+
|
| 14 |
+
def __init__(self, seed: int = 42):
|
| 15 |
+
"""
|
| 16 |
+
Initialize dataset builder.
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
seed: Random seed for reproducibility
|
| 20 |
+
"""
|
| 21 |
+
self.seed = seed
|
| 22 |
+
random.seed(seed)
|
| 23 |
+
|
| 24 |
+
def train_test_split(
|
| 25 |
+
self,
|
| 26 |
+
data: List[Dict[str, Any]],
|
| 27 |
+
train_ratio: float = 0.8,
|
| 28 |
+
val_ratio: float = 0.1,
|
| 29 |
+
test_ratio: float = 0.1,
|
| 30 |
+
shuffle: bool = True
|
| 31 |
+
) -> Tuple[List[Dict], List[Dict], List[Dict]]:
|
| 32 |
+
"""
|
| 33 |
+
Split data into train/validation/test sets.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
data: List of data examples
|
| 37 |
+
train_ratio: Fraction for training
|
| 38 |
+
val_ratio: Fraction for validation
|
| 39 |
+
test_ratio: Fraction for testing
|
| 40 |
+
shuffle: Whether to shuffle data
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
Tuple of (train_data, val_data, test_data)
|
| 44 |
+
"""
|
| 45 |
+
# Validate ratios
|
| 46 |
+
total = train_ratio + val_ratio + test_ratio
|
| 47 |
+
if abs(total - 1.0) > 0.01:
|
| 48 |
+
raise ValueError(f"Ratios must sum to 1.0, got {total}")
|
| 49 |
+
|
| 50 |
+
# Shuffle if requested
|
| 51 |
+
data_copy = data.copy()
|
| 52 |
+
if shuffle:
|
| 53 |
+
random.shuffle(data_copy)
|
| 54 |
+
|
| 55 |
+
# Calculate split indices
|
| 56 |
+
n = len(data_copy)
|
| 57 |
+
train_end = int(n * train_ratio)
|
| 58 |
+
val_end = train_end + int(n * val_ratio)
|
| 59 |
+
|
| 60 |
+
# Split
|
| 61 |
+
train_data = data_copy[:train_end]
|
| 62 |
+
val_data = data_copy[train_end:val_end]
|
| 63 |
+
test_data = data_copy[val_end:]
|
| 64 |
+
|
| 65 |
+
return train_data, val_data, test_data
|
| 66 |
+
|
| 67 |
+
def create_balanced_split(
|
| 68 |
+
self,
|
| 69 |
+
data: List[Dict[str, Any]],
|
| 70 |
+
category_key: str,
|
| 71 |
+
train_ratio: float = 0.8
|
| 72 |
+
) -> Tuple[List[Dict], List[Dict]]:
|
| 73 |
+
"""
|
| 74 |
+
Create balanced train/test split by category.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
data: List of data examples
|
| 78 |
+
category_key: Key for category field
|
| 79 |
+
train_ratio: Fraction for training
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
Tuple of (train_data, test_data)
|
| 83 |
+
"""
|
| 84 |
+
# Group by category
|
| 85 |
+
categories = {}
|
| 86 |
+
for example in data:
|
| 87 |
+
cat = example.get(category_key, "unknown")
|
| 88 |
+
if cat not in categories:
|
| 89 |
+
categories[cat] = []
|
| 90 |
+
categories[cat].append(example)
|
| 91 |
+
|
| 92 |
+
# Split each category
|
| 93 |
+
train_data = []
|
| 94 |
+
test_data = []
|
| 95 |
+
|
| 96 |
+
for cat, examples in categories.items():
|
| 97 |
+
random.shuffle(examples)
|
| 98 |
+
split_idx = int(len(examples) * train_ratio)
|
| 99 |
+
train_data.extend(examples[:split_idx])
|
| 100 |
+
test_data.extend(examples[split_idx:])
|
| 101 |
+
|
| 102 |
+
# Shuffle final datasets
|
| 103 |
+
random.shuffle(train_data)
|
| 104 |
+
random.shuffle(test_data)
|
| 105 |
+
|
| 106 |
+
return train_data, test_data
|
| 107 |
+
|
| 108 |
+
def save_split(
|
| 109 |
+
self,
|
| 110 |
+
train_data: List[Dict],
|
| 111 |
+
val_data: List[Dict],
|
| 112 |
+
test_data: List[Dict],
|
| 113 |
+
output_dir: str
|
| 114 |
+
) -> None:
|
| 115 |
+
"""
|
| 116 |
+
Save split datasets to files.
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
train_data: Training data
|
| 120 |
+
val_data: Validation data
|
| 121 |
+
test_data: Test data
|
| 122 |
+
output_dir: Output directory
|
| 123 |
+
"""
|
| 124 |
+
import json
|
| 125 |
+
from pathlib import Path
|
| 126 |
+
|
| 127 |
+
output_path = Path(output_dir)
|
| 128 |
+
output_path.mkdir(parents=True, exist_ok=True)
|
| 129 |
+
|
| 130 |
+
# Save each split
|
| 131 |
+
with open(output_path / "train.json", 'w') as f:
|
| 132 |
+
json.dump(train_data, f, indent=2)
|
| 133 |
+
|
| 134 |
+
with open(output_path / "val.json", 'w') as f:
|
| 135 |
+
json.dump(val_data, f, indent=2)
|
| 136 |
+
|
| 137 |
+
with open(output_path / "test.json", 'w') as f:
|
| 138 |
+
json.dump(test_data, f, indent=2)
|
| 139 |
+
|
| 140 |
+
print(f"✅ Datasets saved to {output_dir}")
|
| 141 |
+
print(f" Train: {len(train_data)} examples")
|
| 142 |
+
print(f" Val: {len(val_data)} examples")
|
| 143 |
+
print(f" Test: {len(test_data)} examples")
|
data_aggregation/hf_dataset_loader.py
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
HuggingFace Dataset Loader
|
| 3 |
+
|
| 4 |
+
Downloads and converts HuggingFace datasets into financial advisor training format.
|
| 5 |
+
Supports transaction categorization, financial Q&A, and other financial datasets.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from datasets import load_dataset
|
| 9 |
+
from typing import List, Dict, Any, Optional
|
| 10 |
+
import random
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class HuggingFaceDatasetLoader:
|
| 15 |
+
"""Load and convert HuggingFace datasets for financial advisor training"""
|
| 16 |
+
|
| 17 |
+
def __init__(self):
|
| 18 |
+
# Popular financial datasets on HuggingFace (publicly accessible)
|
| 19 |
+
self.known_datasets = {
|
| 20 |
+
# Core Financial Q&A Datasets
|
| 21 |
+
"financial-alpaca": {
|
| 22 |
+
"path": "gbharti/finance-alpaca",
|
| 23 |
+
"type": "qa",
|
| 24 |
+
"description": "Financial Q&A dataset based on Alpaca format (52K examples)",
|
| 25 |
+
"category": "General Finance"
|
| 26 |
+
},
|
| 27 |
+
"fingpt-finred": {
|
| 28 |
+
"path": "FinGPT/fingpt-finred",
|
| 29 |
+
"type": "qa",
|
| 30 |
+
"description": "Financial relation extraction dataset",
|
| 31 |
+
"category": "Financial Analysis"
|
| 32 |
+
},
|
| 33 |
+
"finance-qa-10k": {
|
| 34 |
+
"path": "virattt/financial-qa-10K",
|
| 35 |
+
"type": "qa",
|
| 36 |
+
"description": "Financial Q&A from 10-K filings",
|
| 37 |
+
"category": "SEC Filings"
|
| 38 |
+
},
|
| 39 |
+
|
| 40 |
+
# Financial News & Sentiment
|
| 41 |
+
"financial-phrasebank": {
|
| 42 |
+
"path": "financial_phrasebank",
|
| 43 |
+
"type": "qa",
|
| 44 |
+
"description": "Financial news sentiment analysis (4.8K sentences)",
|
| 45 |
+
"category": "Sentiment Analysis"
|
| 46 |
+
},
|
| 47 |
+
"fin-sentiment": {
|
| 48 |
+
"path": "zeroshot/twitter-financial-news-sentiment",
|
| 49 |
+
"type": "qa",
|
| 50 |
+
"description": "Financial news sentiment from Twitter (11K examples)",
|
| 51 |
+
"category": "Sentiment Analysis"
|
| 52 |
+
},
|
| 53 |
+
|
| 54 |
+
# Investment & Trading
|
| 55 |
+
"stock-market-qa": {
|
| 56 |
+
"path": "virattt/financial-qa-10K",
|
| 57 |
+
"type": "qa",
|
| 58 |
+
"description": "Stock market Q&A from 10-K filings",
|
| 59 |
+
"category": "Investments"
|
| 60 |
+
},
|
| 61 |
+
"sec-edgar-filings": {
|
| 62 |
+
"path": "JanosAudron/financial-reports-sec",
|
| 63 |
+
"type": "qa",
|
| 64 |
+
"description": "SEC EDGAR financial reports",
|
| 65 |
+
"category": "SEC Filings"
|
| 66 |
+
},
|
| 67 |
+
|
| 68 |
+
# Banking & Risk
|
| 69 |
+
"credit-card-fraud": {
|
| 70 |
+
"path": "nelsoncode/credit-card-fraud",
|
| 71 |
+
"type": "transaction",
|
| 72 |
+
"description": "Credit card fraud detection dataset",
|
| 73 |
+
"category": "Fraud Detection"
|
| 74 |
+
},
|
| 75 |
+
|
| 76 |
+
# Economics & Policy
|
| 77 |
+
"econ-qa": {
|
| 78 |
+
"path": "ChanceFocus/econ-qa",
|
| 79 |
+
"type": "qa",
|
| 80 |
+
"description": "Economics Q&A dataset",
|
| 81 |
+
"category": "Economics"
|
| 82 |
+
},
|
| 83 |
+
|
| 84 |
+
# Instruction Following
|
| 85 |
+
"finance-instructions": {
|
| 86 |
+
"path": "rombodawg/MegaCodeTraining",
|
| 87 |
+
"type": "qa",
|
| 88 |
+
"description": "Financial instruction following dataset",
|
| 89 |
+
"category": "Instruction Following"
|
| 90 |
+
},
|
| 91 |
+
|
| 92 |
+
# Multi-Domain Financial
|
| 93 |
+
"fin-llama": {
|
| 94 |
+
"path": "bavest/fin-llama-dataset",
|
| 95 |
+
"type": "qa",
|
| 96 |
+
"description": "Multi-domain financial dataset for LLaMA",
|
| 97 |
+
"category": "General Finance"
|
| 98 |
+
},
|
| 99 |
+
"finance-chat": {
|
| 100 |
+
"path": "sujet-ai/Sujet-Finance-Instruct-177k",
|
| 101 |
+
"type": "qa",
|
| 102 |
+
"description": "Finance chat instructions (177K examples)",
|
| 103 |
+
"category": "General Finance"
|
| 104 |
+
},
|
| 105 |
+
|
| 106 |
+
# Specialized Financial Topics
|
| 107 |
+
"accounting-qa": {
|
| 108 |
+
"path": "0-hero/OIG-small-chip2",
|
| 109 |
+
"type": "qa",
|
| 110 |
+
"description": "Accounting and bookkeeping Q&A",
|
| 111 |
+
"category": "Accounting"
|
| 112 |
+
},
|
| 113 |
+
"tax-qa": {
|
| 114 |
+
"path": "Locutusque/Tax-assistant",
|
| 115 |
+
"type": "qa",
|
| 116 |
+
"description": "Tax-related questions and answers",
|
| 117 |
+
"category": "Tax & Legal"
|
| 118 |
+
},
|
| 119 |
+
|
| 120 |
+
# Financial Education
|
| 121 |
+
"fin-education": {
|
| 122 |
+
"path": "FinGPT/fingpt-fineval",
|
| 123 |
+
"type": "qa",
|
| 124 |
+
"description": "Financial education and evaluation dataset",
|
| 125 |
+
"category": "Education"
|
| 126 |
+
},
|
| 127 |
+
|
| 128 |
+
# Real Estate & Mortgages
|
| 129 |
+
"real-estate-qa": {
|
| 130 |
+
"path": "0-hero/OIG-small-chip2",
|
| 131 |
+
"type": "qa",
|
| 132 |
+
"description": "Real estate and mortgage Q&A",
|
| 133 |
+
"category": "Real Estate"
|
| 134 |
+
},
|
| 135 |
+
|
| 136 |
+
# Insurance
|
| 137 |
+
"insurance-qa": {
|
| 138 |
+
"path": "0-hero/OIG-small-chip2",
|
| 139 |
+
"type": "qa",
|
| 140 |
+
"description": "Insurance-related questions and answers",
|
| 141 |
+
"category": "Insurance"
|
| 142 |
+
},
|
| 143 |
+
|
| 144 |
+
# Cryptocurrency & DeFi
|
| 145 |
+
"crypto-qa": {
|
| 146 |
+
"path": "Locutusque/hercules-v5.0",
|
| 147 |
+
"type": "qa",
|
| 148 |
+
"description": "Cryptocurrency and DeFi Q&A",
|
| 149 |
+
"category": "Cryptocurrency"
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
def get_preset_datasets(self) -> Dict[str, Dict[str, str]]:
|
| 154 |
+
"""
|
| 155 |
+
Get dictionary of preset datasets
|
| 156 |
+
Returns the known_datasets dictionary
|
| 157 |
+
"""
|
| 158 |
+
return self.known_datasets
|
| 159 |
+
|
| 160 |
+
def load_dataset_by_name(self, dataset_name: str, split: str = "train", max_examples: Optional[int] = None):
|
| 161 |
+
"""
|
| 162 |
+
Load a known dataset by name
|
| 163 |
+
|
| 164 |
+
Args:
|
| 165 |
+
dataset_name: Short name from known_datasets
|
| 166 |
+
split: Dataset split (train/test/validation)
|
| 167 |
+
max_examples: Maximum number of examples to load
|
| 168 |
+
|
| 169 |
+
Returns:
|
| 170 |
+
List of examples in Q&A format
|
| 171 |
+
"""
|
| 172 |
+
if dataset_name not in self.known_datasets:
|
| 173 |
+
raise ValueError(f"Unknown dataset: {dataset_name}. Choose from: {list(self.known_datasets.keys())}")
|
| 174 |
+
|
| 175 |
+
dataset_info = self.known_datasets[dataset_name]
|
| 176 |
+
return self.load_dataset_by_path(
|
| 177 |
+
dataset_info["path"],
|
| 178 |
+
dataset_type=dataset_info["type"],
|
| 179 |
+
split=split,
|
| 180 |
+
max_examples=max_examples
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
def load_dataset_by_path(self, dataset_path: str, dataset_type: str = "auto",
|
| 184 |
+
split: str = "train", max_examples: Optional[int] = None):
|
| 185 |
+
"""
|
| 186 |
+
Load a dataset from HuggingFace by path
|
| 187 |
+
|
| 188 |
+
Args:
|
| 189 |
+
dataset_path: Full path like "gbharti/finance-alpaca"
|
| 190 |
+
dataset_type: Type of dataset (transaction/qa/auto)
|
| 191 |
+
split: Dataset split
|
| 192 |
+
max_examples: Maximum examples to load
|
| 193 |
+
|
| 194 |
+
Returns:
|
| 195 |
+
List of examples in Q&A format
|
| 196 |
+
"""
|
| 197 |
+
print(f"Loading dataset: {dataset_path} (split: {split})...")
|
| 198 |
+
|
| 199 |
+
try:
|
| 200 |
+
# Get HuggingFace token from environment if available
|
| 201 |
+
hf_token = os.environ.get("HUGGINGFACE_TOKEN") or os.environ.get("HF_TOKEN")
|
| 202 |
+
|
| 203 |
+
# Load from HuggingFace with authentication
|
| 204 |
+
try:
|
| 205 |
+
if hf_token:
|
| 206 |
+
dataset = load_dataset(dataset_path, split=split, token=hf_token)
|
| 207 |
+
else:
|
| 208 |
+
dataset = load_dataset(dataset_path, split=split)
|
| 209 |
+
except Exception as auth_error:
|
| 210 |
+
error_msg = str(auth_error)
|
| 211 |
+
if "gated" in error_msg.lower() or "authenticated" in error_msg.lower():
|
| 212 |
+
raise Exception(
|
| 213 |
+
f"Dataset '{dataset_path}' requires authentication.\n\n"
|
| 214 |
+
f"This is a GATED dataset that requires special access.\n\n"
|
| 215 |
+
f"To use this dataset:\n"
|
| 216 |
+
f"1. Go to https://huggingface.co/datasets/{dataset_path}\n"
|
| 217 |
+
f"2. Click 'Access repository' and accept terms\n"
|
| 218 |
+
f"3. Make sure your HuggingFace token is set in Settings tab\n\n"
|
| 219 |
+
f"Or try one of the publicly accessible datasets instead:\n"
|
| 220 |
+
f"- gbharti/finance-alpaca (52K financial Q&A)\n"
|
| 221 |
+
f"- FinGPT/fingpt-finred (Financial relations)\n"
|
| 222 |
+
f"- virattt/financial-qa-10K (10-K filings Q&A)"
|
| 223 |
+
)
|
| 224 |
+
else:
|
| 225 |
+
raise
|
| 226 |
+
|
| 227 |
+
# Limit examples if requested
|
| 228 |
+
if max_examples and len(dataset) > max_examples:
|
| 229 |
+
# Sample randomly for diversity
|
| 230 |
+
indices = random.sample(range(len(dataset)), max_examples)
|
| 231 |
+
dataset = dataset.select(indices)
|
| 232 |
+
|
| 233 |
+
print(f"Loaded {len(dataset)} examples")
|
| 234 |
+
|
| 235 |
+
# Auto-detect type if needed
|
| 236 |
+
if dataset_type == "auto":
|
| 237 |
+
dataset_type = self._detect_dataset_type(dataset[0])
|
| 238 |
+
print(f"Auto-detected type: {dataset_type}")
|
| 239 |
+
|
| 240 |
+
# Convert to Q&A format
|
| 241 |
+
if dataset_type == "transaction":
|
| 242 |
+
converted = self._convert_transaction_dataset(dataset)
|
| 243 |
+
elif dataset_type == "qa":
|
| 244 |
+
converted = self._convert_qa_dataset(dataset)
|
| 245 |
+
else:
|
| 246 |
+
raise ValueError(f"Unsupported dataset type: {dataset_type}")
|
| 247 |
+
|
| 248 |
+
print(f"Converted {len(converted)} examples to Q&A format")
|
| 249 |
+
return converted
|
| 250 |
+
|
| 251 |
+
except Exception as e:
|
| 252 |
+
raise Exception(f"{str(e)}")
|
| 253 |
+
|
| 254 |
+
def _detect_dataset_type(self, example: Dict[str, Any]) -> str:
|
| 255 |
+
"""Auto-detect dataset type from first example"""
|
| 256 |
+
keys = set(example.keys())
|
| 257 |
+
|
| 258 |
+
# Check for transaction data
|
| 259 |
+
if "transaction" in keys or "category" in keys or "amount" in keys:
|
| 260 |
+
return "transaction"
|
| 261 |
+
|
| 262 |
+
# Check for Q&A data
|
| 263 |
+
if ("question" in keys and "answer" in keys) or \
|
| 264 |
+
("instruction" in keys and "output" in keys) or \
|
| 265 |
+
("input" in keys and "output" in keys):
|
| 266 |
+
return "qa"
|
| 267 |
+
|
| 268 |
+
return "unknown"
|
| 269 |
+
|
| 270 |
+
def _convert_transaction_dataset(self, dataset) -> List[Dict[str, str]]:
|
| 271 |
+
"""
|
| 272 |
+
Convert transaction categorization dataset to Q&A format
|
| 273 |
+
|
| 274 |
+
Creates questions like:
|
| 275 |
+
Q: "Categorize this transaction: $50.00 at Starbucks"
|
| 276 |
+
A: "This transaction should be categorized as 'Food & Dining'..."
|
| 277 |
+
"""
|
| 278 |
+
converted = []
|
| 279 |
+
|
| 280 |
+
for item in dataset:
|
| 281 |
+
# Extract fields (adapt to actual dataset structure)
|
| 282 |
+
if "transaction" in item and "category" in item:
|
| 283 |
+
transaction_text = item["transaction"]
|
| 284 |
+
category = item["category"]
|
| 285 |
+
|
| 286 |
+
# Get amount if available
|
| 287 |
+
amount = item.get("amount", "")
|
| 288 |
+
merchant = item.get("merchant", "")
|
| 289 |
+
|
| 290 |
+
# Create Q&A pair
|
| 291 |
+
qa_pair = self._create_transaction_qa(transaction_text, category, amount, merchant)
|
| 292 |
+
converted.append(qa_pair)
|
| 293 |
+
|
| 294 |
+
# Handle alternate structures
|
| 295 |
+
elif "text" in item and "label" in item:
|
| 296 |
+
text = item["text"]
|
| 297 |
+
label = item["label"]
|
| 298 |
+
qa_pair = self._create_transaction_qa(text, label, "", "")
|
| 299 |
+
converted.append(qa_pair)
|
| 300 |
+
|
| 301 |
+
return converted
|
| 302 |
+
|
| 303 |
+
def _create_transaction_qa(self, transaction: str, category: str, amount: str, merchant: str) -> Dict[str, str]:
|
| 304 |
+
"""Create a Q&A pair from transaction data"""
|
| 305 |
+
|
| 306 |
+
# Build transaction description
|
| 307 |
+
transaction_desc = transaction
|
| 308 |
+
|
| 309 |
+
if amount and merchant:
|
| 310 |
+
transaction_desc = f"{amount} at {merchant}"
|
| 311 |
+
elif amount:
|
| 312 |
+
transaction_desc = f"{amount} - {transaction}"
|
| 313 |
+
elif merchant:
|
| 314 |
+
transaction_desc = f"{merchant} - {transaction}"
|
| 315 |
+
|
| 316 |
+
# Create question (vary the format)
|
| 317 |
+
question_templates = [
|
| 318 |
+
f"What category should this transaction be in: {transaction_desc}?",
|
| 319 |
+
f"How would you categorize this transaction: {transaction_desc}?",
|
| 320 |
+
f"Categorize this expense: {transaction_desc}",
|
| 321 |
+
f"Which spending category does this belong to: {transaction_desc}?",
|
| 322 |
+
f"Help me categorize: {transaction_desc}"
|
| 323 |
+
]
|
| 324 |
+
|
| 325 |
+
question = random.choice(question_templates)
|
| 326 |
+
|
| 327 |
+
# Create detailed answer
|
| 328 |
+
answer = self._generate_transaction_answer(transaction_desc, category)
|
| 329 |
+
|
| 330 |
+
return {
|
| 331 |
+
"instruction": question,
|
| 332 |
+
"input": "",
|
| 333 |
+
"output": answer
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
def _generate_transaction_answer(self, transaction: str, category: str) -> str:
|
| 337 |
+
"""Generate a detailed answer for transaction categorization"""
|
| 338 |
+
|
| 339 |
+
# Common category explanations
|
| 340 |
+
category_explanations = {
|
| 341 |
+
"Food & Dining": "restaurants, groceries, coffee shops, and food delivery services",
|
| 342 |
+
"Shopping": "retail purchases, online shopping, clothing, and general merchandise",
|
| 343 |
+
"Transportation": "gas, public transit, ride-sharing services, parking, and vehicle maintenance",
|
| 344 |
+
"Bills & Utilities": "electricity, water, internet, phone bills, and subscriptions",
|
| 345 |
+
"Entertainment": "movies, concerts, streaming services, hobbies, and recreational activities",
|
| 346 |
+
"Health & Fitness": "gym memberships, medical expenses, pharmacy purchases, and wellness services",
|
| 347 |
+
"Travel": "flights, hotels, vacation expenses, and travel-related costs",
|
| 348 |
+
"Personal Care": "haircuts, spa services, cosmetics, and personal grooming",
|
| 349 |
+
"Education": "tuition, books, courses, and educational materials",
|
| 350 |
+
"Gifts & Donations": "charitable contributions, gifts, and donations",
|
| 351 |
+
"Home": "rent, mortgage, furniture, home improvement, and household supplies",
|
| 352 |
+
"Insurance": "health insurance, car insurance, life insurance, and other policies",
|
| 353 |
+
"Fees & Charges": "bank fees, ATM fees, service charges, and late fees",
|
| 354 |
+
"Income": "salary, wages, refunds, and other income sources",
|
| 355 |
+
"Investments": "stock purchases, retirement contributions, and investment transactions"
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
# Get explanation or use generic
|
| 359 |
+
explanation = category_explanations.get(
|
| 360 |
+
category,
|
| 361 |
+
f"expenses related to {category.lower()}"
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
# Generate answer
|
| 365 |
+
answer = f"This transaction should be categorized as '{category}'. "
|
| 366 |
+
answer += f"This category typically includes {explanation}. "
|
| 367 |
+
answer += f"\n\nBy tracking expenses in the '{category}' category, you can better understand your spending patterns "
|
| 368 |
+
answer += f"and make informed decisions about your budget. "
|
| 369 |
+
|
| 370 |
+
# Add budgeting tip based on category
|
| 371 |
+
if category in ["Food & Dining", "Shopping", "Entertainment"]:
|
| 372 |
+
answer += f"Consider setting a monthly budget limit for {category} to help control discretionary spending."
|
| 373 |
+
elif category in ["Bills & Utilities", "Insurance"]:
|
| 374 |
+
answer += f"These are typically fixed expenses that should be factored into your monthly budget planning."
|
| 375 |
+
elif category in ["Health & Fitness", "Education"]:
|
| 376 |
+
answer += f"These are investments in yourself that can provide long-term value and returns."
|
| 377 |
+
elif category == "Income":
|
| 378 |
+
answer += f"Regular income tracking helps you understand your cash flow and plan your savings goals."
|
| 379 |
+
|
| 380 |
+
return answer
|
| 381 |
+
|
| 382 |
+
def _convert_qa_dataset(self, dataset) -> List[Dict[str, str]]:
|
| 383 |
+
"""
|
| 384 |
+
Convert Q&A dataset to standard format
|
| 385 |
+
|
| 386 |
+
Handles various Q&A formats from HuggingFace
|
| 387 |
+
"""
|
| 388 |
+
converted = []
|
| 389 |
+
|
| 390 |
+
for item in dataset:
|
| 391 |
+
qa_pair = {}
|
| 392 |
+
|
| 393 |
+
# Try different field name combinations
|
| 394 |
+
if "instruction" in item and "output" in item:
|
| 395 |
+
qa_pair = {
|
| 396 |
+
"instruction": item["instruction"],
|
| 397 |
+
"input": item.get("input", ""),
|
| 398 |
+
"output": item["output"]
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
elif "question" in item and "answer" in item:
|
| 402 |
+
qa_pair = {
|
| 403 |
+
"instruction": item["question"],
|
| 404 |
+
"input": item.get("context", ""),
|
| 405 |
+
"output": item["answer"]
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
elif "prompt" in item and "response" in item:
|
| 409 |
+
qa_pair = {
|
| 410 |
+
"instruction": item["prompt"],
|
| 411 |
+
"input": "",
|
| 412 |
+
"output": item["response"]
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
elif "text" in item:
|
| 416 |
+
# Try to parse conversational format
|
| 417 |
+
text = item["text"]
|
| 418 |
+
if "Human:" in text and "Assistant:" in text:
|
| 419 |
+
parts = text.split("Assistant:")
|
| 420 |
+
if len(parts) >= 2:
|
| 421 |
+
question = parts[0].replace("Human:", "").strip()
|
| 422 |
+
answer = parts[1].strip()
|
| 423 |
+
qa_pair = {
|
| 424 |
+
"instruction": question,
|
| 425 |
+
"input": "",
|
| 426 |
+
"output": answer
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
if qa_pair:
|
| 430 |
+
converted.append(qa_pair)
|
| 431 |
+
|
| 432 |
+
return converted
|
| 433 |
+
|
| 434 |
+
def list_available_datasets(self) -> List[Dict[str, str]]:
|
| 435 |
+
"""List all known financial datasets"""
|
| 436 |
+
datasets = []
|
| 437 |
+
|
| 438 |
+
for name, info in self.known_datasets.items():
|
| 439 |
+
datasets.append({
|
| 440 |
+
"name": name,
|
| 441 |
+
"path": info["path"],
|
| 442 |
+
"type": info["type"],
|
| 443 |
+
"description": info["description"]
|
| 444 |
+
})
|
| 445 |
+
|
| 446 |
+
return datasets
|
| 447 |
+
|
| 448 |
+
def preview_dataset(self, dataset_path: str, num_examples: int = 3) -> str:
|
| 449 |
+
"""
|
| 450 |
+
Preview a dataset before loading
|
| 451 |
+
|
| 452 |
+
Args:
|
| 453 |
+
dataset_path: HuggingFace dataset path
|
| 454 |
+
num_examples: Number of examples to show
|
| 455 |
+
|
| 456 |
+
Returns:
|
| 457 |
+
Formatted preview string
|
| 458 |
+
"""
|
| 459 |
+
try:
|
| 460 |
+
# Get HuggingFace token from environment if available
|
| 461 |
+
hf_token = os.environ.get("HUGGINGFACE_TOKEN") or os.environ.get("HF_TOKEN")
|
| 462 |
+
|
| 463 |
+
# Load small sample with authentication
|
| 464 |
+
try:
|
| 465 |
+
if hf_token:
|
| 466 |
+
dataset = load_dataset(dataset_path, split="train", streaming=False, token=hf_token)
|
| 467 |
+
else:
|
| 468 |
+
dataset = load_dataset(dataset_path, split="train", streaming=False)
|
| 469 |
+
except Exception as auth_error:
|
| 470 |
+
error_msg = str(auth_error)
|
| 471 |
+
if "gated" in error_msg.lower() or "authenticated" in error_msg.lower():
|
| 472 |
+
return (
|
| 473 |
+
f"⚠️ Dataset '{dataset_path}' requires authentication.\n\n"
|
| 474 |
+
f"This is a GATED dataset. To preview:\n"
|
| 475 |
+
f"1. Visit: https://huggingface.co/datasets/{dataset_path}\n"
|
| 476 |
+
f"2. Click 'Access repository' and accept terms\n"
|
| 477 |
+
f"3. Set your HuggingFace token in Settings tab\n\n"
|
| 478 |
+
f"Try these publicly accessible datasets instead:\n"
|
| 479 |
+
f"- gbharti/finance-alpaca\n"
|
| 480 |
+
f"- FinGPT/fingpt-finred\n"
|
| 481 |
+
f"- virattt/financial-qa-10K"
|
| 482 |
+
)
|
| 483 |
+
else:
|
| 484 |
+
return f"Error: {auth_error}"
|
| 485 |
+
|
| 486 |
+
# Get first N examples
|
| 487 |
+
sample_size = min(num_examples, len(dataset))
|
| 488 |
+
samples = dataset.select(range(sample_size))
|
| 489 |
+
|
| 490 |
+
preview = f"Dataset: {dataset_path}\n"
|
| 491 |
+
preview += f"Total examples: {len(dataset)}\n"
|
| 492 |
+
preview += f"Fields: {list(samples[0].keys())}\n\n"
|
| 493 |
+
preview += "Sample examples:\n"
|
| 494 |
+
preview += "=" * 60 + "\n\n"
|
| 495 |
+
|
| 496 |
+
for i, example in enumerate(samples, 1):
|
| 497 |
+
preview += f"Example {i}:\n"
|
| 498 |
+
for key, value in example.items():
|
| 499 |
+
value_str = str(value)[:100]
|
| 500 |
+
preview += f" {key}: {value_str}\n"
|
| 501 |
+
preview += "\n"
|
| 502 |
+
|
| 503 |
+
return preview
|
| 504 |
+
|
| 505 |
+
except Exception as e:
|
| 506 |
+
return f"Error previewing dataset: {e}"
|
| 507 |
+
|
| 508 |
+
def get_dataset_info(self, dataset_path: str) -> Dict[str, Any]:
|
| 509 |
+
"""Get metadata about a dataset"""
|
| 510 |
+
try:
|
| 511 |
+
from datasets import get_dataset_config_names, get_dataset_split_names
|
| 512 |
+
|
| 513 |
+
configs = get_dataset_config_names(dataset_path)
|
| 514 |
+
splits = get_dataset_split_names(dataset_path)
|
| 515 |
+
|
| 516 |
+
return {
|
| 517 |
+
"path": dataset_path,
|
| 518 |
+
"configs": configs,
|
| 519 |
+
"splits": splits,
|
| 520 |
+
"status": "available"
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
except Exception as e:
|
| 524 |
+
return {
|
| 525 |
+
"path": dataset_path,
|
| 526 |
+
"error": str(e),
|
| 527 |
+
"status": "error"
|
| 528 |
+
}
|
data_aggregation/hf_dataset_loader.py.tmp.15260.1762137822282
ADDED
|
File without changes
|
data_aggregation/preprocessors.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data Preprocessors Module
|
| 3 |
+
|
| 4 |
+
Provides text preprocessing and data cleaning utilities.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
from typing import List, Dict, Any
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TextPreprocessor:
|
| 12 |
+
"""Text preprocessing utilities."""
|
| 13 |
+
|
| 14 |
+
@staticmethod
|
| 15 |
+
def clean_text(text: str) -> str:
|
| 16 |
+
"""
|
| 17 |
+
Clean and normalize text.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
text: Input text
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
Cleaned text
|
| 24 |
+
"""
|
| 25 |
+
# Remove extra whitespace
|
| 26 |
+
text = re.sub(r'\s+', ' ', text)
|
| 27 |
+
# Strip leading/trailing whitespace
|
| 28 |
+
text = text.strip()
|
| 29 |
+
return text
|
| 30 |
+
|
| 31 |
+
@staticmethod
|
| 32 |
+
def remove_special_chars(text: str, keep_chars: str = " .,!?-") -> str:
|
| 33 |
+
"""
|
| 34 |
+
Remove special characters.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
text: Input text
|
| 38 |
+
keep_chars: Characters to keep
|
| 39 |
+
|
| 40 |
+
Returns:
|
| 41 |
+
Text with special chars removed
|
| 42 |
+
"""
|
| 43 |
+
pattern = f"[^a-zA-Z0-9{re.escape(keep_chars)}]"
|
| 44 |
+
return re.sub(pattern, '', text)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class DataCleaner:
|
| 48 |
+
"""Data cleaning utilities."""
|
| 49 |
+
|
| 50 |
+
@staticmethod
|
| 51 |
+
def remove_duplicates(data: List[Dict[str, Any]], key: str = "instruction") -> List[Dict[str, Any]]:
|
| 52 |
+
"""
|
| 53 |
+
Remove duplicate examples.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
data: List of data examples
|
| 57 |
+
key: Key to check for duplicates
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
Deduplicated data
|
| 61 |
+
"""
|
| 62 |
+
seen = set()
|
| 63 |
+
unique_data = []
|
| 64 |
+
|
| 65 |
+
for example in data:
|
| 66 |
+
value = example.get(key, "")
|
| 67 |
+
if value and value not in seen:
|
| 68 |
+
seen.add(value)
|
| 69 |
+
unique_data.append(example)
|
| 70 |
+
|
| 71 |
+
return unique_data
|
| 72 |
+
|
| 73 |
+
@staticmethod
|
| 74 |
+
def filter_by_length(
|
| 75 |
+
data: List[Dict[str, Any]],
|
| 76 |
+
min_length: int = 10,
|
| 77 |
+
max_length: int = 10000,
|
| 78 |
+
key: str = "output"
|
| 79 |
+
) -> List[Dict[str, Any]]:
|
| 80 |
+
"""
|
| 81 |
+
Filter examples by length.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
data: List of data examples
|
| 85 |
+
min_length: Minimum text length
|
| 86 |
+
max_length: Maximum text length
|
| 87 |
+
key: Key to check length
|
| 88 |
+
|
| 89 |
+
Returns:
|
| 90 |
+
Filtered data
|
| 91 |
+
"""
|
| 92 |
+
filtered = []
|
| 93 |
+
for example in data:
|
| 94 |
+
text = example.get(key, "")
|
| 95 |
+
if min_length <= len(text) <= max_length:
|
| 96 |
+
filtered.append(example)
|
| 97 |
+
|
| 98 |
+
return filtered
|
data_aggregation/quality_validator.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Quality Validator Module
|
| 3 |
+
|
| 4 |
+
Validates training data quality on multiple dimensions.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
from typing import List, Dict, Any, Tuple
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class QualityValidator:
|
| 12 |
+
"""Validate and score training data quality."""
|
| 13 |
+
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
min_length: int = 10,
|
| 17 |
+
max_length: int = 5000,
|
| 18 |
+
min_score: float = 60.0
|
| 19 |
+
):
|
| 20 |
+
"""
|
| 21 |
+
Initialize quality validator.
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
min_length: Minimum text length
|
| 25 |
+
max_length: Maximum text length
|
| 26 |
+
min_score: Minimum quality score threshold (0-100)
|
| 27 |
+
"""
|
| 28 |
+
self.min_length = min_length
|
| 29 |
+
self.max_length = max_length
|
| 30 |
+
self.min_score = min_score
|
| 31 |
+
|
| 32 |
+
def validate_example(self, example: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
| 33 |
+
"""
|
| 34 |
+
Validate a single example.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
example: Data example to validate
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
Tuple of (is_valid, list_of_issues)
|
| 41 |
+
"""
|
| 42 |
+
issues = []
|
| 43 |
+
|
| 44 |
+
# Check required fields
|
| 45 |
+
if "instruction" not in example:
|
| 46 |
+
issues.append("Missing 'instruction' field")
|
| 47 |
+
if "output" not in example:
|
| 48 |
+
issues.append("Missing 'output' field")
|
| 49 |
+
|
| 50 |
+
if issues:
|
| 51 |
+
return False, issues
|
| 52 |
+
|
| 53 |
+
# Check lengths
|
| 54 |
+
instruction = example.get("instruction", "")
|
| 55 |
+
output = example.get("output", "")
|
| 56 |
+
|
| 57 |
+
if len(instruction) < 5:
|
| 58 |
+
issues.append("Instruction too short")
|
| 59 |
+
if len(output) < self.min_length:
|
| 60 |
+
issues.append(f"Output too short (min {self.min_length} chars)")
|
| 61 |
+
if len(output) > self.max_length:
|
| 62 |
+
issues.append(f"Output too long (max {self.max_length} chars)")
|
| 63 |
+
|
| 64 |
+
# Check for empty output
|
| 65 |
+
if not output.strip():
|
| 66 |
+
issues.append("Empty output")
|
| 67 |
+
|
| 68 |
+
return len(issues) == 0, issues
|
| 69 |
+
|
| 70 |
+
def score_example(self, example: Dict[str, Any]) -> float:
|
| 71 |
+
"""
|
| 72 |
+
Score example quality (0-100).
|
| 73 |
+
|
| 74 |
+
Scoring dimensions:
|
| 75 |
+
- Length appropriateness
|
| 76 |
+
- Completeness
|
| 77 |
+
- Coherence (basic checks)
|
| 78 |
+
|
| 79 |
+
Args:
|
| 80 |
+
example: Data example
|
| 81 |
+
|
| 82 |
+
Returns:
|
| 83 |
+
Quality score (0-100)
|
| 84 |
+
"""
|
| 85 |
+
score = 100.0
|
| 86 |
+
|
| 87 |
+
# Check validity first
|
| 88 |
+
is_valid, issues = self.validate_example(example)
|
| 89 |
+
if not is_valid:
|
| 90 |
+
score -= 20.0 * len(issues)
|
| 91 |
+
|
| 92 |
+
if score <= 0:
|
| 93 |
+
return 0.0
|
| 94 |
+
|
| 95 |
+
# Length scoring
|
| 96 |
+
output = example.get("output", "")
|
| 97 |
+
output_len = len(output)
|
| 98 |
+
|
| 99 |
+
if output_len < self.min_length:
|
| 100 |
+
score -= 20.0
|
| 101 |
+
elif output_len > self.max_length:
|
| 102 |
+
score -= 10.0
|
| 103 |
+
|
| 104 |
+
# Coherence checks
|
| 105 |
+
# Check for repetition
|
| 106 |
+
words = output.lower().split()
|
| 107 |
+
if len(words) > 0:
|
| 108 |
+
unique_ratio = len(set(words)) / len(words)
|
| 109 |
+
if unique_ratio < 0.3: # Too repetitive
|
| 110 |
+
score -= 30.0
|
| 111 |
+
|
| 112 |
+
# Check for proper sentences (basic)
|
| 113 |
+
sentences = re.split(r'[.!?]+', output)
|
| 114 |
+
valid_sentences = [s for s in sentences if len(s.strip()) > 10]
|
| 115 |
+
if len(valid_sentences) == 0:
|
| 116 |
+
score -= 20.0
|
| 117 |
+
|
| 118 |
+
# Check for gibberish (basic)
|
| 119 |
+
if output_len > 20:
|
| 120 |
+
# Check if output has reasonable word length distribution
|
| 121 |
+
avg_word_len = sum(len(w) for w in words) / max(len(words), 1)
|
| 122 |
+
if avg_word_len > 15 or avg_word_len < 2: # Likely gibberish
|
| 123 |
+
score -= 25.0
|
| 124 |
+
|
| 125 |
+
return max(0.0, min(100.0, score))
|
| 126 |
+
|
| 127 |
+
def validate_batch(
|
| 128 |
+
self,
|
| 129 |
+
data: List[Dict[str, Any]],
|
| 130 |
+
verbose: bool = False
|
| 131 |
+
) -> Dict[str, Any]:
|
| 132 |
+
"""
|
| 133 |
+
Validate a batch of examples.
|
| 134 |
+
|
| 135 |
+
Args:
|
| 136 |
+
data: List of data examples
|
| 137 |
+
verbose: Print detailed validation info
|
| 138 |
+
|
| 139 |
+
Returns:
|
| 140 |
+
Validation results dict
|
| 141 |
+
"""
|
| 142 |
+
valid_data = []
|
| 143 |
+
invalid_data = []
|
| 144 |
+
scores = []
|
| 145 |
+
|
| 146 |
+
for i, example in enumerate(data):
|
| 147 |
+
is_valid, issues = self.validate_example(example)
|
| 148 |
+
score = self.score_example(example)
|
| 149 |
+
scores.append(score)
|
| 150 |
+
|
| 151 |
+
if is_valid and score >= self.min_score:
|
| 152 |
+
valid_data.append(example)
|
| 153 |
+
else:
|
| 154 |
+
invalid_data.append({
|
| 155 |
+
"example": example,
|
| 156 |
+
"issues": issues,
|
| 157 |
+
"score": score
|
| 158 |
+
})
|
| 159 |
+
|
| 160 |
+
if verbose:
|
| 161 |
+
print(f"Example {i} failed validation (score: {score:.1f})")
|
| 162 |
+
for issue in issues:
|
| 163 |
+
print(f" - {issue}")
|
| 164 |
+
|
| 165 |
+
avg_score = sum(scores) / len(scores) if scores else 0.0
|
| 166 |
+
|
| 167 |
+
results = {
|
| 168 |
+
"total": len(data),
|
| 169 |
+
"valid": len(valid_data),
|
| 170 |
+
"invalid": len(invalid_data),
|
| 171 |
+
"pass_rate": len(valid_data) / len(data) if data else 0.0,
|
| 172 |
+
"avg_score": avg_score,
|
| 173 |
+
"valid_data": valid_data,
|
| 174 |
+
"invalid_data": invalid_data
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
if verbose:
|
| 178 |
+
print(f"\n✅ Validation complete:")
|
| 179 |
+
print(f" Total: {results['total']}")
|
| 180 |
+
print(f" Valid: {results['valid']}")
|
| 181 |
+
print(f" Invalid: {results['invalid']}")
|
| 182 |
+
print(f" Pass rate: {results['pass_rate']*100:.1f}%")
|
| 183 |
+
print(f" Avg score: {avg_score:.1f}")
|
| 184 |
+
|
| 185 |
+
return results
|
| 186 |
+
|
| 187 |
+
def filter_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
| 188 |
+
"""
|
| 189 |
+
Filter data, keeping only valid examples.
|
| 190 |
+
|
| 191 |
+
Args:
|
| 192 |
+
data: List of data examples
|
| 193 |
+
|
| 194 |
+
Returns:
|
| 195 |
+
Filtered valid data
|
| 196 |
+
"""
|
| 197 |
+
results = self.validate_batch(data)
|
| 198 |
+
return results["valid_data"]
|
| 199 |
+
|
| 200 |
+
def get_quality_report(self, data: List[Dict[str, Any]]) -> str:
|
| 201 |
+
"""
|
| 202 |
+
Generate a quality report for the data.
|
| 203 |
+
|
| 204 |
+
Args:
|
| 205 |
+
data: List of data examples
|
| 206 |
+
|
| 207 |
+
Returns:
|
| 208 |
+
Formatted quality report
|
| 209 |
+
"""
|
| 210 |
+
results = self.validate_batch(data)
|
| 211 |
+
|
| 212 |
+
report = f"""
|
| 213 |
+
DATA QUALITY REPORT
|
| 214 |
+
==================
|
| 215 |
+
|
| 216 |
+
Total Examples: {results['total']}
|
| 217 |
+
Valid Examples: {results['valid']}
|
| 218 |
+
Invalid Examples: {results['invalid']}
|
| 219 |
+
Pass Rate: {results['pass_rate']*100:.1f}%
|
| 220 |
+
Average Quality Score: {results['avg_score']:.1f}/100
|
| 221 |
+
|
| 222 |
+
"""
|
| 223 |
+
|
| 224 |
+
if results['invalid_data']:
|
| 225 |
+
report += "COMMON ISSUES:\n"
|
| 226 |
+
issue_counts = {}
|
| 227 |
+
for item in results['invalid_data']:
|
| 228 |
+
for issue in item['issues']:
|
| 229 |
+
issue_counts[issue] = issue_counts.get(issue, 0) + 1
|
| 230 |
+
|
| 231 |
+
for issue, count in sorted(issue_counts.items(), key=lambda x: x[1], reverse=True):
|
| 232 |
+
report += f" - {issue}: {count} examples\n"
|
| 233 |
+
|
| 234 |
+
return report
|
data_aggregation/synthetic_generator.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Synthetic Data Generator Module
|
| 3 |
+
|
| 4 |
+
Generates synthetic training data using LLM APIs (OpenAI, Anthropic).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
from typing import List, Dict, Any, Optional
|
| 10 |
+
import time
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class SyntheticDataGenerator:
|
| 14 |
+
"""Generate synthetic training data using LLMs."""
|
| 15 |
+
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
api_provider: str = "openai",
|
| 19 |
+
api_key: Optional[str] = None,
|
| 20 |
+
model: Optional[str] = None
|
| 21 |
+
):
|
| 22 |
+
"""
|
| 23 |
+
Initialize synthetic data generator.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
api_provider: "openai" or "anthropic"
|
| 27 |
+
api_key: API key (uses environment variable if None)
|
| 28 |
+
model: Model name (uses default if None)
|
| 29 |
+
"""
|
| 30 |
+
self.api_provider = api_provider.lower()
|
| 31 |
+
self.api_key = api_key or os.getenv("OPENAI_API_KEY" if self.api_provider == "openai" else "ANTHROPIC_API_KEY")
|
| 32 |
+
|
| 33 |
+
if self.api_provider == "openai":
|
| 34 |
+
self.model = model or "gpt-4-turbo-preview"
|
| 35 |
+
else:
|
| 36 |
+
self.model = model or "claude-3-opus-20240229"
|
| 37 |
+
|
| 38 |
+
self.client = None
|
| 39 |
+
self._init_client()
|
| 40 |
+
|
| 41 |
+
def _init_client(self):
|
| 42 |
+
"""Initialize API client."""
|
| 43 |
+
try:
|
| 44 |
+
if self.api_provider == "openai":
|
| 45 |
+
from openai import OpenAI
|
| 46 |
+
self.client = OpenAI(api_key=self.api_key)
|
| 47 |
+
else:
|
| 48 |
+
from anthropic import Anthropic
|
| 49 |
+
self.client = Anthropic(api_key=self.api_key)
|
| 50 |
+
except ImportError:
|
| 51 |
+
print(f"Warning: {self.api_provider} library not installed")
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f"Warning: Failed to initialize {self.api_provider} client: {e}")
|
| 54 |
+
|
| 55 |
+
def generate_examples(
|
| 56 |
+
self,
|
| 57 |
+
num_examples: int,
|
| 58 |
+
topics: List[str],
|
| 59 |
+
difficulty: str = "mixed",
|
| 60 |
+
domain: str = "financial_advisor"
|
| 61 |
+
) -> List[Dict[str, Any]]:
|
| 62 |
+
"""
|
| 63 |
+
Generate synthetic training examples.
|
| 64 |
+
|
| 65 |
+
Args:
|
| 66 |
+
num_examples: Number of examples to generate
|
| 67 |
+
topics: List of topics to cover
|
| 68 |
+
difficulty: "beginner", "intermediate", "advanced", or "mixed"
|
| 69 |
+
domain: Domain context
|
| 70 |
+
|
| 71 |
+
Returns:
|
| 72 |
+
List of generated examples
|
| 73 |
+
"""
|
| 74 |
+
if not self.client:
|
| 75 |
+
raise ValueError(f"API client not initialized. Check {self.api_provider} API key.")
|
| 76 |
+
|
| 77 |
+
print(f"Generating {num_examples} examples using {self.api_provider}...")
|
| 78 |
+
|
| 79 |
+
examples = []
|
| 80 |
+
examples_per_topic = max(1, num_examples // len(topics))
|
| 81 |
+
|
| 82 |
+
for topic in topics:
|
| 83 |
+
for i in range(examples_per_topic):
|
| 84 |
+
if len(examples) >= num_examples:
|
| 85 |
+
break
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
example = self._generate_single_example(topic, difficulty, domain)
|
| 89 |
+
if example:
|
| 90 |
+
examples.append(example)
|
| 91 |
+
print(f"Generated {len(examples)}/{num_examples}", end="\r")
|
| 92 |
+
time.sleep(0.5) # Rate limiting
|
| 93 |
+
except Exception as e:
|
| 94 |
+
print(f"\nError generating example: {e}")
|
| 95 |
+
continue
|
| 96 |
+
|
| 97 |
+
print(f"\n✅ Generated {len(examples)} examples")
|
| 98 |
+
return examples
|
| 99 |
+
|
| 100 |
+
def _generate_single_example(
|
| 101 |
+
self,
|
| 102 |
+
topic: str,
|
| 103 |
+
difficulty: str,
|
| 104 |
+
domain: str
|
| 105 |
+
) -> Optional[Dict[str, Any]]:
|
| 106 |
+
"""Generate a single training example."""
|
| 107 |
+
|
| 108 |
+
prompt = f"""Generate a realistic {domain} training example about {topic}.
|
| 109 |
+
Difficulty level: {difficulty}
|
| 110 |
+
|
| 111 |
+
Output format (JSON):
|
| 112 |
+
{{
|
| 113 |
+
"instruction": "The user's question or request",
|
| 114 |
+
"input": "Additional context (optional, can be empty string)",
|
| 115 |
+
"output": "The detailed, helpful response"
|
| 116 |
+
}}
|
| 117 |
+
|
| 118 |
+
Make it realistic and detailed. The response should be informative and professional."""
|
| 119 |
+
|
| 120 |
+
try:
|
| 121 |
+
if self.api_provider == "openai":
|
| 122 |
+
response = self.client.chat.completions.create(
|
| 123 |
+
model=self.model,
|
| 124 |
+
messages=[
|
| 125 |
+
{"role": "system", "content": "You are a data generation assistant. Output only valid JSON."},
|
| 126 |
+
{"role": "user", "content": prompt}
|
| 127 |
+
],
|
| 128 |
+
temperature=0.8
|
| 129 |
+
)
|
| 130 |
+
content = response.choices[0].message.content
|
| 131 |
+
else: # anthropic
|
| 132 |
+
response = self.client.messages.create(
|
| 133 |
+
model=self.model,
|
| 134 |
+
max_tokens=1024,
|
| 135 |
+
messages=[
|
| 136 |
+
{"role": "user", "content": prompt}
|
| 137 |
+
],
|
| 138 |
+
temperature=0.8
|
| 139 |
+
)
|
| 140 |
+
content = response.content[0].text
|
| 141 |
+
|
| 142 |
+
# Parse JSON
|
| 143 |
+
# Remove markdown code blocks if present
|
| 144 |
+
content = content.strip()
|
| 145 |
+
if content.startswith("```"):
|
| 146 |
+
content = content.split("```")[1]
|
| 147 |
+
if content.startswith("json"):
|
| 148 |
+
content = content[4:]
|
| 149 |
+
content = content.strip()
|
| 150 |
+
|
| 151 |
+
example = json.loads(content)
|
| 152 |
+
|
| 153 |
+
# Validate structure
|
| 154 |
+
if "instruction" in example and "output" in example:
|
| 155 |
+
if "input" not in example:
|
| 156 |
+
example["input"] = ""
|
| 157 |
+
return example
|
| 158 |
+
|
| 159 |
+
except Exception as e:
|
| 160 |
+
print(f"\nError parsing example: {e}")
|
| 161 |
+
|
| 162 |
+
return None
|
| 163 |
+
|
| 164 |
+
def generate_from_scenarios(
|
| 165 |
+
self,
|
| 166 |
+
scenarios: List[Dict[str, Any]],
|
| 167 |
+
num_examples_per_scenario: int = 1
|
| 168 |
+
) -> List[Dict[str, Any]]:
|
| 169 |
+
"""
|
| 170 |
+
Generate examples from predefined scenarios.
|
| 171 |
+
|
| 172 |
+
Args:
|
| 173 |
+
scenarios: List of scenario dicts with context
|
| 174 |
+
num_examples_per_scenario: Examples to generate per scenario
|
| 175 |
+
|
| 176 |
+
Returns:
|
| 177 |
+
Generated examples
|
| 178 |
+
"""
|
| 179 |
+
examples = []
|
| 180 |
+
|
| 181 |
+
for scenario in scenarios:
|
| 182 |
+
for _ in range(num_examples_per_scenario):
|
| 183 |
+
prompt = f"""Based on this scenario, generate a training example:
|
| 184 |
+
|
| 185 |
+
Scenario: {json.dumps(scenario, indent=2)}
|
| 186 |
+
|
| 187 |
+
Output format (JSON):
|
| 188 |
+
{{
|
| 189 |
+
"instruction": "The user's question or request",
|
| 190 |
+
"input": "Additional context based on scenario",
|
| 191 |
+
"output": "The detailed, helpful response"
|
| 192 |
+
}}"""
|
| 193 |
+
|
| 194 |
+
try:
|
| 195 |
+
example = self._generate_from_prompt(prompt)
|
| 196 |
+
if example:
|
| 197 |
+
examples.append(example)
|
| 198 |
+
except Exception as e:
|
| 199 |
+
print(f"Error: {e}")
|
| 200 |
+
continue
|
| 201 |
+
|
| 202 |
+
return examples
|
| 203 |
+
|
| 204 |
+
def _generate_from_prompt(self, prompt: str) -> Optional[Dict[str, Any]]:
|
| 205 |
+
"""Generate example from custom prompt."""
|
| 206 |
+
try:
|
| 207 |
+
if self.api_provider == "openai":
|
| 208 |
+
response = self.client.chat.completions.create(
|
| 209 |
+
model=self.model,
|
| 210 |
+
messages=[{"role": "user", "content": prompt}],
|
| 211 |
+
temperature=0.8
|
| 212 |
+
)
|
| 213 |
+
content = response.choices[0].message.content
|
| 214 |
+
else:
|
| 215 |
+
response = self.client.messages.create(
|
| 216 |
+
model=self.model,
|
| 217 |
+
max_tokens=1024,
|
| 218 |
+
messages=[{"role": "user", "content": prompt}]
|
| 219 |
+
)
|
| 220 |
+
content = response.content[0].text
|
| 221 |
+
|
| 222 |
+
# Parse JSON
|
| 223 |
+
content = content.strip()
|
| 224 |
+
if "```" in content:
|
| 225 |
+
content = content.split("```")[1]
|
| 226 |
+
if content.startswith("json"):
|
| 227 |
+
content = content[4:]
|
| 228 |
+
content = content.strip()
|
| 229 |
+
|
| 230 |
+
return json.loads(content)
|
| 231 |
+
|
| 232 |
+
except Exception as e:
|
| 233 |
+
print(f"Error: {e}")
|
| 234 |
+
return None
|
data_aggregation/tool_use_generator.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Tool Use Generator Module
|
| 3 |
+
|
| 4 |
+
Generates training data for teaching LLMs to use tools and functions.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
from typing import List, Dict, Any, Optional
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ToolUseGenerator:
|
| 12 |
+
"""Generate tool use training data for function calling."""
|
| 13 |
+
|
| 14 |
+
def __init__(self, tools: Optional[List[Dict]] = None):
|
| 15 |
+
"""
|
| 16 |
+
Initialize tool use generator.
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
tools: List of tool definitions
|
| 20 |
+
"""
|
| 21 |
+
self.tools = tools or self._get_default_financial_tools()
|
| 22 |
+
|
| 23 |
+
def _get_default_financial_tools(self) -> List[Dict]:
|
| 24 |
+
"""Get default financial tools."""
|
| 25 |
+
return [
|
| 26 |
+
{
|
| 27 |
+
"name": "get_market_data",
|
| 28 |
+
"description": "Get current market data for a stock",
|
| 29 |
+
"parameters": {
|
| 30 |
+
"symbol": "string",
|
| 31 |
+
"data_type": "string (price, volume, market_cap)"
|
| 32 |
+
},
|
| 33 |
+
"returns": "dict"
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"name": "calculate_compound_interest",
|
| 37 |
+
"description": "Calculate compound interest",
|
| 38 |
+
"parameters": {
|
| 39 |
+
"principal": "float",
|
| 40 |
+
"rate": "float",
|
| 41 |
+
"time": "float",
|
| 42 |
+
"frequency": "int"
|
| 43 |
+
},
|
| 44 |
+
"returns": "float"
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"name": "get_portfolio_allocation",
|
| 48 |
+
"description": "Get current portfolio allocation",
|
| 49 |
+
"parameters": {
|
| 50 |
+
"user_id": "string"
|
| 51 |
+
},
|
| 52 |
+
"returns": "dict"
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"name": "calculate_retirement_needs",
|
| 56 |
+
"description": "Calculate retirement savings needs",
|
| 57 |
+
"parameters": {
|
| 58 |
+
"current_age": "int",
|
| 59 |
+
"retirement_age": "int",
|
| 60 |
+
"current_savings": "float",
|
| 61 |
+
"annual_contribution": "float"
|
| 62 |
+
},
|
| 63 |
+
"returns": "dict"
|
| 64 |
+
}
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
def generate_tool_use_examples(
|
| 68 |
+
self,
|
| 69 |
+
num_examples: int = 10,
|
| 70 |
+
complexity: str = "single"
|
| 71 |
+
) -> List[Dict[str, Any]]:
|
| 72 |
+
"""
|
| 73 |
+
Generate tool use training examples.
|
| 74 |
+
|
| 75 |
+
Args:
|
| 76 |
+
num_examples: Number of examples to generate
|
| 77 |
+
complexity: "single" or "multi" (multi-step tool chains)
|
| 78 |
+
|
| 79 |
+
Returns:
|
| 80 |
+
List of tool use examples
|
| 81 |
+
"""
|
| 82 |
+
examples = []
|
| 83 |
+
|
| 84 |
+
for _ in range(num_examples):
|
| 85 |
+
if complexity == "single":
|
| 86 |
+
example = self._generate_single_tool_example()
|
| 87 |
+
else:
|
| 88 |
+
example = self._generate_multi_tool_example()
|
| 89 |
+
|
| 90 |
+
if example:
|
| 91 |
+
examples.append(example)
|
| 92 |
+
|
| 93 |
+
return examples
|
| 94 |
+
|
| 95 |
+
def _generate_single_tool_example(self) -> Dict[str, Any]:
|
| 96 |
+
"""Generate example with single tool call."""
|
| 97 |
+
import random
|
| 98 |
+
|
| 99 |
+
tool = random.choice(self.tools)
|
| 100 |
+
|
| 101 |
+
# Create example based on tool
|
| 102 |
+
if tool["name"] == "get_market_data":
|
| 103 |
+
return {
|
| 104 |
+
"instruction": "What is the current price of Apple stock?",
|
| 105 |
+
"input": "",
|
| 106 |
+
"output": "I'll check the current market data for Apple stock.",
|
| 107 |
+
"tool_calls": [
|
| 108 |
+
{
|
| 109 |
+
"tool": "get_market_data",
|
| 110 |
+
"parameters": {
|
| 111 |
+
"symbol": "AAPL",
|
| 112 |
+
"data_type": "price"
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
]
|
| 116 |
+
}
|
| 117 |
+
elif tool["name"] == "calculate_compound_interest":
|
| 118 |
+
return {
|
| 119 |
+
"instruction": "If I invest $10,000 at 5% interest compounded quarterly, how much will I have in 10 years?",
|
| 120 |
+
"input": "",
|
| 121 |
+
"output": "I'll calculate that for you using compound interest formula.",
|
| 122 |
+
"tool_calls": [
|
| 123 |
+
{
|
| 124 |
+
"tool": "calculate_compound_interest",
|
| 125 |
+
"parameters": {
|
| 126 |
+
"principal": 10000,
|
| 127 |
+
"rate": 0.05,
|
| 128 |
+
"time": 10,
|
| 129 |
+
"frequency": 4
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
]
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
# Generic example
|
| 136 |
+
return {
|
| 137 |
+
"instruction": f"Use the {tool['name']} tool",
|
| 138 |
+
"input": "",
|
| 139 |
+
"output": f"I'll use {tool['name']} to help with that.",
|
| 140 |
+
"tool_calls": [{"tool": tool["name"], "parameters": {}}]
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
def _generate_multi_tool_example(self) -> Dict[str, Any]:
|
| 144 |
+
"""Generate example with multiple tool calls (chain)."""
|
| 145 |
+
return {
|
| 146 |
+
"instruction": "Should I rebalance my portfolio given current market conditions?",
|
| 147 |
+
"input": "",
|
| 148 |
+
"output": "Let me check your portfolio and current market data to provide a recommendation.",
|
| 149 |
+
"tool_calls": [
|
| 150 |
+
{
|
| 151 |
+
"tool": "get_portfolio_allocation",
|
| 152 |
+
"parameters": {"user_id": "user123"}
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"tool": "get_market_data",
|
| 156 |
+
"parameters": {"symbol": "SPY", "data_type": "price"}
|
| 157 |
+
}
|
| 158 |
+
],
|
| 159 |
+
"reasoning": "First get the user's current portfolio allocation, then check market conditions, and finally provide rebalancing advice."
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
def add_custom_tool(self, tool: Dict[str, Any]) -> None:
|
| 163 |
+
"""
|
| 164 |
+
Add a custom tool definition.
|
| 165 |
+
|
| 166 |
+
Args:
|
| 167 |
+
tool: Tool definition dict
|
| 168 |
+
"""
|
| 169 |
+
required_keys = ["name", "description", "parameters", "returns"]
|
| 170 |
+
if not all(key in tool for key in required_keys):
|
| 171 |
+
raise ValueError(f"Tool must have keys: {required_keys}")
|
| 172 |
+
|
| 173 |
+
self.tools.append(tool)
|
| 174 |
+
|
| 175 |
+
def get_tools_schema(self) -> List[Dict]:
|
| 176 |
+
"""Get OpenAI-compatible tools schema."""
|
| 177 |
+
schema = []
|
| 178 |
+
|
| 179 |
+
for tool in self.tools:
|
| 180 |
+
schema.append({
|
| 181 |
+
"type": "function",
|
| 182 |
+
"function": {
|
| 183 |
+
"name": tool["name"],
|
| 184 |
+
"description": tool["description"],
|
| 185 |
+
"parameters": {
|
| 186 |
+
"type": "object",
|
| 187 |
+
"properties": tool["parameters"],
|
| 188 |
+
"required": list(tool["parameters"].keys())
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
})
|
| 192 |
+
|
| 193 |
+
return schema
|
domain_templates/__init__.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Domain Templates for AURA AI Training System
|
| 3 |
+
|
| 4 |
+
This module provides pre-configured domain templates for different AI assistant specializations:
|
| 5 |
+
- Financial Advisor: Wealth management, investing, retirement planning
|
| 6 |
+
- Medical Assistant: Health information, symptom understanding, wellness
|
| 7 |
+
- Legal Advisor: Legal information, document understanding, rights explanation
|
| 8 |
+
- Education Tutor: Academic subjects, test prep, homework help
|
| 9 |
+
- Custom Domain: User-defined domain specifications
|
| 10 |
+
|
| 11 |
+
Each domain provides:
|
| 12 |
+
- Topics for training data generation
|
| 13 |
+
- System prompts for model behavior
|
| 14 |
+
- Example questions for testing
|
| 15 |
+
- Specialized tools for function calling
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from .base_domain import BaseDomain
|
| 19 |
+
from .financial_advisor import FinancialAdvisorDomain
|
| 20 |
+
from .medical_assistant import MedicalAssistantDomain
|
| 21 |
+
from .legal_advisor import LegalAdvisorDomain
|
| 22 |
+
from .education_tutor import EducationTutorDomain
|
| 23 |
+
from .custom_domain import CustomDomain
|
| 24 |
+
|
| 25 |
+
# Domain registry
|
| 26 |
+
_DOMAIN_REGISTRY = {
|
| 27 |
+
'financial_advisor': FinancialAdvisorDomain,
|
| 28 |
+
'medical_assistant': MedicalAssistantDomain,
|
| 29 |
+
'legal_advisor': LegalAdvisorDomain,
|
| 30 |
+
'education_tutor': EducationTutorDomain,
|
| 31 |
+
'custom': CustomDomain
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def get_domain(domain_key: str, **kwargs) -> BaseDomain:
|
| 36 |
+
"""
|
| 37 |
+
Get a domain instance by key.
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
domain_key: Domain identifier (financial_advisor, medical_assistant, etc.)
|
| 41 |
+
**kwargs: Additional arguments for domain initialization (used for custom domains)
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
Domain instance
|
| 45 |
+
|
| 46 |
+
Raises:
|
| 47 |
+
ValueError: If domain_key is not recognized
|
| 48 |
+
|
| 49 |
+
Examples:
|
| 50 |
+
>>> domain = get_domain('financial_advisor')
|
| 51 |
+
>>> domain.name
|
| 52 |
+
'Financial Advisor'
|
| 53 |
+
|
| 54 |
+
>>> custom = get_domain('custom', name='Cooking Assistant', icon='👨🍳')
|
| 55 |
+
>>> custom.name
|
| 56 |
+
'Cooking Assistant'
|
| 57 |
+
"""
|
| 58 |
+
if domain_key not in _DOMAIN_REGISTRY:
|
| 59 |
+
raise ValueError(
|
| 60 |
+
f"Unknown domain: {domain_key}. "
|
| 61 |
+
f"Available domains: {', '.join(_DOMAIN_REGISTRY.keys())}"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
domain_class = _DOMAIN_REGISTRY[domain_key]
|
| 65 |
+
|
| 66 |
+
# CustomDomain accepts kwargs, others don't
|
| 67 |
+
if domain_key == 'custom':
|
| 68 |
+
return domain_class(**kwargs)
|
| 69 |
+
else:
|
| 70 |
+
return domain_class()
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def list_domains() -> dict:
|
| 74 |
+
"""
|
| 75 |
+
List all available domains with their basic information.
|
| 76 |
+
|
| 77 |
+
Returns:
|
| 78 |
+
Dictionary mapping domain keys to domain info dicts
|
| 79 |
+
Each info dict contains: name, description, icon
|
| 80 |
+
|
| 81 |
+
Example:
|
| 82 |
+
>>> domains = list_domains()
|
| 83 |
+
>>> domains['financial_advisor']['name']
|
| 84 |
+
'Financial Advisor'
|
| 85 |
+
"""
|
| 86 |
+
domains = {}
|
| 87 |
+
for key, domain_class in _DOMAIN_REGISTRY.items():
|
| 88 |
+
if key == 'custom':
|
| 89 |
+
# Custom domain needs special handling
|
| 90 |
+
domain = domain_class()
|
| 91 |
+
else:
|
| 92 |
+
domain = domain_class()
|
| 93 |
+
|
| 94 |
+
domains[key] = {
|
| 95 |
+
'key': key,
|
| 96 |
+
'name': domain.name,
|
| 97 |
+
'description': domain.description,
|
| 98 |
+
'icon': domain.icon
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
return domains
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def register_domain(key: str, domain_class: type) -> None:
|
| 105 |
+
"""
|
| 106 |
+
Register a custom domain class.
|
| 107 |
+
|
| 108 |
+
Args:
|
| 109 |
+
key: Domain identifier
|
| 110 |
+
domain_class: Domain class (must inherit from BaseDomain)
|
| 111 |
+
|
| 112 |
+
Raises:
|
| 113 |
+
TypeError: If domain_class doesn't inherit from BaseDomain
|
| 114 |
+
ValueError: If key already exists
|
| 115 |
+
|
| 116 |
+
Example:
|
| 117 |
+
>>> class MyDomain(BaseDomain):
|
| 118 |
+
... pass
|
| 119 |
+
>>> register_domain('my_domain', MyDomain)
|
| 120 |
+
"""
|
| 121 |
+
if not issubclass(domain_class, BaseDomain):
|
| 122 |
+
raise TypeError(f"{domain_class} must inherit from BaseDomain")
|
| 123 |
+
|
| 124 |
+
if key in _DOMAIN_REGISTRY:
|
| 125 |
+
raise ValueError(f"Domain key '{key}' already registered")
|
| 126 |
+
|
| 127 |
+
_DOMAIN_REGISTRY[key] = domain_class
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# Export all public classes and functions
|
| 131 |
+
__all__ = [
|
| 132 |
+
'BaseDomain',
|
| 133 |
+
'FinancialAdvisorDomain',
|
| 134 |
+
'MedicalAssistantDomain',
|
| 135 |
+
'LegalAdvisorDomain',
|
| 136 |
+
'EducationTutorDomain',
|
| 137 |
+
'CustomDomain',
|
| 138 |
+
'get_domain',
|
| 139 |
+
'list_domains',
|
| 140 |
+
'register_domain'
|
| 141 |
+
]
|
domain_templates/base_domain.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Base domain template class for AURA AI training system.
|
| 3 |
+
"""
|
| 4 |
+
from abc import ABC, abstractmethod
|
| 5 |
+
from typing import List
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class BaseDomain(ABC):
|
| 9 |
+
"""
|
| 10 |
+
Abstract base class for domain templates.
|
| 11 |
+
|
| 12 |
+
Each domain provides:
|
| 13 |
+
- Name and description
|
| 14 |
+
- Icon for UI display
|
| 15 |
+
- Topic lists for training data generation
|
| 16 |
+
- System prompts for model behavior
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(self):
|
| 20 |
+
self._name = ""
|
| 21 |
+
self._description = ""
|
| 22 |
+
self._icon = ""
|
| 23 |
+
|
| 24 |
+
@property
|
| 25 |
+
def name(self) -> str:
|
| 26 |
+
"""Domain name (human-readable)"""
|
| 27 |
+
return self._name
|
| 28 |
+
|
| 29 |
+
@property
|
| 30 |
+
def description(self) -> str:
|
| 31 |
+
"""Domain description"""
|
| 32 |
+
return self._description
|
| 33 |
+
|
| 34 |
+
@property
|
| 35 |
+
def icon(self) -> str:
|
| 36 |
+
"""Domain icon (emoji)"""
|
| 37 |
+
return self._icon
|
| 38 |
+
|
| 39 |
+
@abstractmethod
|
| 40 |
+
def get_topics(self) -> List[str]:
|
| 41 |
+
"""
|
| 42 |
+
Get list of topics for this domain.
|
| 43 |
+
|
| 44 |
+
Returns:
|
| 45 |
+
List of topic strings for training data generation
|
| 46 |
+
"""
|
| 47 |
+
pass
|
| 48 |
+
|
| 49 |
+
@abstractmethod
|
| 50 |
+
def get_system_prompt(self) -> str:
|
| 51 |
+
"""
|
| 52 |
+
Get system prompt for this domain.
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
System prompt string that defines model behavior
|
| 56 |
+
"""
|
| 57 |
+
pass
|
| 58 |
+
|
| 59 |
+
def get_example_questions(self) -> List[str]:
|
| 60 |
+
"""
|
| 61 |
+
Get example questions for this domain.
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
List of example questions for testing
|
| 65 |
+
"""
|
| 66 |
+
return []
|
| 67 |
+
|
| 68 |
+
def get_specialized_tools(self) -> List[dict]:
|
| 69 |
+
"""
|
| 70 |
+
Get specialized tools for this domain.
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
List of tool definitions for function calling
|
| 74 |
+
"""
|
| 75 |
+
return []
|
| 76 |
+
|
| 77 |
+
def get_tools(self) -> List[dict]:
|
| 78 |
+
"""
|
| 79 |
+
Get tools for this domain (alias for get_specialized_tools).
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
List of tool definitions for function calling
|
| 83 |
+
"""
|
| 84 |
+
return self.get_specialized_tools()
|
| 85 |
+
|
| 86 |
+
def get_benchmarks(self) -> List[dict]:
|
| 87 |
+
"""
|
| 88 |
+
Get pre-built benchmarks for this domain.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
List of benchmark definitions
|
| 92 |
+
"""
|
| 93 |
+
return []
|
domain_templates/custom_domain.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Custom domain template for user-defined domains.
|
| 3 |
+
"""
|
| 4 |
+
from typing import List
|
| 5 |
+
from .base_domain import BaseDomain
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class CustomDomain(BaseDomain):
|
| 9 |
+
"""
|
| 10 |
+
Custom domain template that allows users to define
|
| 11 |
+
their own domain specifications.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
name: str = "Custom Domain",
|
| 17 |
+
description: str = "User-defined custom domain",
|
| 18 |
+
icon: str = "🎯",
|
| 19 |
+
topics: List[str] = None,
|
| 20 |
+
system_prompt: str = None
|
| 21 |
+
):
|
| 22 |
+
super().__init__()
|
| 23 |
+
self._name = name
|
| 24 |
+
self._description = description
|
| 25 |
+
self._icon = icon
|
| 26 |
+
self._topics = topics or []
|
| 27 |
+
self._system_prompt = system_prompt or self._default_system_prompt()
|
| 28 |
+
|
| 29 |
+
def _default_system_prompt(self) -> str:
|
| 30 |
+
"""Generate default system prompt for custom domain"""
|
| 31 |
+
return f"""You are an expert assistant specialized in {self._name}.
|
| 32 |
+
|
| 33 |
+
Your role is to:
|
| 34 |
+
1. Provide accurate, helpful information in your domain of expertise
|
| 35 |
+
2. Explain concepts clearly and thoroughly
|
| 36 |
+
3. Adapt your responses to the user's level of understanding
|
| 37 |
+
4. Acknowledge the limits of your knowledge
|
| 38 |
+
5. Recommend seeking professional advice when appropriate
|
| 39 |
+
|
| 40 |
+
Guidelines:
|
| 41 |
+
- Be thorough but concise
|
| 42 |
+
- Use examples to illustrate concepts
|
| 43 |
+
- Break down complex topics into understandable parts
|
| 44 |
+
- Stay focused on your area of expertise
|
| 45 |
+
- Always prioritize accuracy and helpfulness"""
|
| 46 |
+
|
| 47 |
+
def get_topics(self) -> List[str]:
|
| 48 |
+
"""Get custom domain topics"""
|
| 49 |
+
if not self._topics:
|
| 50 |
+
return [
|
| 51 |
+
"General Knowledge",
|
| 52 |
+
"Problem Solving",
|
| 53 |
+
"Analysis",
|
| 54 |
+
"Research",
|
| 55 |
+
"Communication"
|
| 56 |
+
]
|
| 57 |
+
return self._topics
|
| 58 |
+
|
| 59 |
+
def get_system_prompt(self) -> str:
|
| 60 |
+
"""Get custom domain system prompt"""
|
| 61 |
+
return self._system_prompt
|
| 62 |
+
|
| 63 |
+
def set_topics(self, topics: List[str]) -> None:
|
| 64 |
+
"""
|
| 65 |
+
Set custom topics for this domain.
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
topics: List of topic strings
|
| 69 |
+
"""
|
| 70 |
+
self._topics = topics
|
| 71 |
+
|
| 72 |
+
def set_system_prompt(self, prompt: str) -> None:
|
| 73 |
+
"""
|
| 74 |
+
Set custom system prompt for this domain.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
prompt: System prompt string
|
| 78 |
+
"""
|
| 79 |
+
self._system_prompt = prompt
|
| 80 |
+
|
| 81 |
+
def set_name(self, name: str) -> None:
|
| 82 |
+
"""
|
| 83 |
+
Set custom domain name.
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
name: Domain name
|
| 87 |
+
"""
|
| 88 |
+
self._name = name
|
| 89 |
+
|
| 90 |
+
def set_description(self, description: str) -> None:
|
| 91 |
+
"""
|
| 92 |
+
Set custom domain description.
|
| 93 |
+
|
| 94 |
+
Args:
|
| 95 |
+
description: Domain description
|
| 96 |
+
"""
|
| 97 |
+
self._description = description
|
| 98 |
+
|
| 99 |
+
def set_icon(self, icon: str) -> None:
|
| 100 |
+
"""
|
| 101 |
+
Set custom domain icon.
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
icon: Icon (emoji) string
|
| 105 |
+
"""
|
| 106 |
+
self._icon = icon
|
| 107 |
+
|
| 108 |
+
def get_example_questions(self) -> List[str]:
|
| 109 |
+
"""Get example questions for custom domain"""
|
| 110 |
+
return [
|
| 111 |
+
f"What are the key concepts in {self._name}?",
|
| 112 |
+
f"Can you explain [topic] in {self._name}?",
|
| 113 |
+
f"What are best practices for {self._name}?",
|
| 114 |
+
f"How do I get started with {self._name}?",
|
| 115 |
+
f"What are common challenges in {self._name}?"
|
| 116 |
+
]
|
| 117 |
+
|
| 118 |
+
def get_specialized_tools(self) -> List[dict]:
|
| 119 |
+
"""Get specialized tools for custom domain"""
|
| 120 |
+
# Custom domains can define their own tools
|
| 121 |
+
# Default: empty list
|
| 122 |
+
return []
|
| 123 |
+
|
| 124 |
+
@classmethod
|
| 125 |
+
def from_config(cls, config: dict) -> 'CustomDomain':
|
| 126 |
+
"""
|
| 127 |
+
Create CustomDomain from configuration dictionary.
|
| 128 |
+
|
| 129 |
+
Args:
|
| 130 |
+
config: Dictionary with keys: name, description, icon, topics, system_prompt
|
| 131 |
+
|
| 132 |
+
Returns:
|
| 133 |
+
CustomDomain instance
|
| 134 |
+
"""
|
| 135 |
+
return cls(
|
| 136 |
+
name=config.get('name', 'Custom Domain'),
|
| 137 |
+
description=config.get('description', 'User-defined custom domain'),
|
| 138 |
+
icon=config.get('icon', '🎯'),
|
| 139 |
+
topics=config.get('topics', []),
|
| 140 |
+
system_prompt=config.get('system_prompt')
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
def to_config(self) -> dict:
|
| 144 |
+
"""
|
| 145 |
+
Export domain configuration as dictionary.
|
| 146 |
+
|
| 147 |
+
Returns:
|
| 148 |
+
Dictionary with domain configuration
|
| 149 |
+
"""
|
| 150 |
+
return {
|
| 151 |
+
'name': self._name,
|
| 152 |
+
'description': self._description,
|
| 153 |
+
'icon': self._icon,
|
| 154 |
+
'topics': self._topics,
|
| 155 |
+
'system_prompt': self._system_prompt
|
| 156 |
+
}
|
domain_templates/education_tutor.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Education Tutor domain template.
|
| 3 |
+
"""
|
| 4 |
+
from typing import List
|
| 5 |
+
from .base_domain import BaseDomain
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class EducationTutorDomain(BaseDomain):
|
| 9 |
+
"""
|
| 10 |
+
Education tutor domain for academic subjects,
|
| 11 |
+
test preparation, and learning assistance.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
def __init__(self):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self._name = "Education Tutor"
|
| 17 |
+
self._description = "Educational tutor assistant for K-12 through college subjects, test preparation, homework help, and learning strategies."
|
| 18 |
+
self._icon = "@"
|
| 19 |
+
|
| 20 |
+
def get_topics(self) -> List[str]:
|
| 21 |
+
"""Get education tutor topics"""
|
| 22 |
+
return [
|
| 23 |
+
"Mathematics - Algebra",
|
| 24 |
+
"Mathematics - Geometry",
|
| 25 |
+
"Mathematics - Calculus",
|
| 26 |
+
"Mathematics - Statistics",
|
| 27 |
+
"Mathematics - Trigonometry",
|
| 28 |
+
"Mathematics - Pre-Calculus",
|
| 29 |
+
"Mathematics - Linear Algebra",
|
| 30 |
+
"Mathematics - Differential Equations",
|
| 31 |
+
"Physics - Mechanics",
|
| 32 |
+
"Physics - Electricity and Magnetism",
|
| 33 |
+
"Physics - Thermodynamics",
|
| 34 |
+
"Physics - Optics",
|
| 35 |
+
"Physics - Quantum Mechanics",
|
| 36 |
+
"Chemistry - General Chemistry",
|
| 37 |
+
"Chemistry - Organic Chemistry",
|
| 38 |
+
"Chemistry - Biochemistry",
|
| 39 |
+
"Biology - Cell Biology",
|
| 40 |
+
"Biology - Genetics",
|
| 41 |
+
"Biology - Ecology",
|
| 42 |
+
"Biology - Anatomy and Physiology",
|
| 43 |
+
"Computer Science - Programming",
|
| 44 |
+
"Computer Science - Data Structures",
|
| 45 |
+
"Computer Science - Algorithms",
|
| 46 |
+
"Computer Science - Databases",
|
| 47 |
+
"English - Grammar and Composition",
|
| 48 |
+
"English - Literature Analysis",
|
| 49 |
+
"English - Essay Writing",
|
| 50 |
+
"English - Reading Comprehension",
|
| 51 |
+
"History - World History",
|
| 52 |
+
"History - US History",
|
| 53 |
+
"History - European History",
|
| 54 |
+
"Social Studies - Geography",
|
| 55 |
+
"Social Studies - Civics",
|
| 56 |
+
"Social Studies - Economics",
|
| 57 |
+
"Foreign Languages - Spanish",
|
| 58 |
+
"Foreign Languages - French",
|
| 59 |
+
"Foreign Languages - German",
|
| 60 |
+
"Foreign Languages - Mandarin",
|
| 61 |
+
"Test Prep - SAT",
|
| 62 |
+
"Test Prep - ACT",
|
| 63 |
+
"Test Prep - GRE",
|
| 64 |
+
"Test Prep - GMAT",
|
| 65 |
+
"Test Prep - MCAT",
|
| 66 |
+
"Test Prep - LSAT",
|
| 67 |
+
"Test Prep - AP Exams",
|
| 68 |
+
"Study Skills and Time Management",
|
| 69 |
+
"Note-taking Strategies",
|
| 70 |
+
"Memory Techniques",
|
| 71 |
+
"Research Methods",
|
| 72 |
+
"Critical Thinking"
|
| 73 |
+
]
|
| 74 |
+
|
| 75 |
+
def get_system_prompt(self) -> str:
|
| 76 |
+
"""Get education tutor system prompt"""
|
| 77 |
+
return """You are an expert educational tutor with deep knowledge across:
|
| 78 |
+
- K-12 and college-level academic subjects
|
| 79 |
+
- Mathematics (algebra through calculus and beyond)
|
| 80 |
+
- Sciences (physics, chemistry, biology)
|
| 81 |
+
- Computer science and programming
|
| 82 |
+
- English, literature, and writing
|
| 83 |
+
- History and social studies
|
| 84 |
+
- Foreign languages
|
| 85 |
+
- Test preparation (SAT, ACT, GRE, AP exams, etc.)
|
| 86 |
+
- Study skills and learning strategies
|
| 87 |
+
|
| 88 |
+
Your role is to:
|
| 89 |
+
1. Explain concepts clearly and build understanding from fundamentals
|
| 90 |
+
2. Use the Socratic method - ask questions to guide learning
|
| 91 |
+
3. Provide step-by-step explanations for problem-solving
|
| 92 |
+
4. Offer multiple approaches and perspectives
|
| 93 |
+
5. Create practice problems and examples
|
| 94 |
+
6. Adapt explanations to the student's level
|
| 95 |
+
7. Encourage critical thinking and independent learning
|
| 96 |
+
8. Identify knowledge gaps and address misconceptions
|
| 97 |
+
|
| 98 |
+
Teaching approach:
|
| 99 |
+
- Start with what the student knows
|
| 100 |
+
- Break complex topics into manageable parts
|
| 101 |
+
- Use analogies and real-world examples
|
| 102 |
+
- Show work step-by-step
|
| 103 |
+
- Explain the "why" behind the "how"
|
| 104 |
+
- Encourage questions and exploration
|
| 105 |
+
- Provide positive, constructive feedback
|
| 106 |
+
- Relate new concepts to previously learned material
|
| 107 |
+
|
| 108 |
+
For homework help:
|
| 109 |
+
- Guide toward the answer rather than giving it directly
|
| 110 |
+
- Help students understand the underlying concepts
|
| 111 |
+
- Teach problem-solving strategies
|
| 112 |
+
- Check understanding with follow-up questions
|
| 113 |
+
|
| 114 |
+
For test preparation:
|
| 115 |
+
- Identify key concepts and common question types
|
| 116 |
+
- Provide practice problems with increasing difficulty
|
| 117 |
+
- Teach test-taking strategies and time management
|
| 118 |
+
- Explain answer choices and common traps
|
| 119 |
+
- Build confidence through mastery
|
| 120 |
+
|
| 121 |
+
Remember: Your goal is to help students truly understand material, not just get correct answers. Foster curiosity, critical thinking, and independent learning skills."""
|
| 122 |
+
|
| 123 |
+
def get_example_questions(self) -> List[str]:
|
| 124 |
+
"""Get example education questions"""
|
| 125 |
+
return [
|
| 126 |
+
"Can you explain how to solve quadratic equations?",
|
| 127 |
+
"What's the difference between mitosis and meiosis?",
|
| 128 |
+
"How do I write a strong thesis statement?",
|
| 129 |
+
"Can you help me understand Newton's laws of motion?",
|
| 130 |
+
"What are the causes of World War I?",
|
| 131 |
+
"How do I balance chemical equations?",
|
| 132 |
+
"Can you explain the Pythagorean theorem?",
|
| 133 |
+
"What's the difference between DNA and RNA?",
|
| 134 |
+
"How should I prepare for the SAT math section?",
|
| 135 |
+
"Can you help me understand Shakespeare's use of metaphor?"
|
| 136 |
+
]
|
| 137 |
+
|
| 138 |
+
def get_specialized_tools(self) -> List[dict]:
|
| 139 |
+
"""Get education tutor tools"""
|
| 140 |
+
return [
|
| 141 |
+
{
|
| 142 |
+
"name": "solve_math_problem",
|
| 143 |
+
"description": "Solve mathematical problems step-by-step",
|
| 144 |
+
"parameters": {
|
| 145 |
+
"problem": "string (equation or word problem)",
|
| 146 |
+
"subject": "string (algebra, calculus, statistics, etc.)",
|
| 147 |
+
"show_steps": "boolean"
|
| 148 |
+
},
|
| 149 |
+
"returns": "Solution with detailed steps"
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
"name": "generate_practice_problems",
|
| 153 |
+
"description": "Generate practice problems for a topic",
|
| 154 |
+
"parameters": {
|
| 155 |
+
"subject": "string",
|
| 156 |
+
"topic": "string",
|
| 157 |
+
"difficulty": "string (easy, medium, hard)",
|
| 158 |
+
"quantity": "int"
|
| 159 |
+
},
|
| 160 |
+
"returns": "List of practice problems with solutions"
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"name": "explain_concept",
|
| 164 |
+
"description": "Provide detailed explanation of academic concept",
|
| 165 |
+
"parameters": {
|
| 166 |
+
"concept": "string",
|
| 167 |
+
"subject": "string",
|
| 168 |
+
"grade_level": "string (elementary, middle, high_school, college)"
|
| 169 |
+
},
|
| 170 |
+
"returns": "Explanation with examples and analogies"
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"name": "check_grammar",
|
| 174 |
+
"description": "Check grammar and writing quality",
|
| 175 |
+
"parameters": {
|
| 176 |
+
"text": "string",
|
| 177 |
+
"check_type": "string (grammar, style, structure, all)"
|
| 178 |
+
},
|
| 179 |
+
"returns": "Grammar corrections and writing suggestions"
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"name": "create_study_guide",
|
| 183 |
+
"description": "Create study guide for a topic",
|
| 184 |
+
"parameters": {
|
| 185 |
+
"subject": "string",
|
| 186 |
+
"topic": "string",
|
| 187 |
+
"format": "string (outline, flashcards, summary, practice_test)"
|
| 188 |
+
},
|
| 189 |
+
"returns": "Formatted study guide"
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"name": "analyze_literary_text",
|
| 193 |
+
"description": "Analyze literary devices and themes",
|
| 194 |
+
"parameters": {
|
| 195 |
+
"text_excerpt": "string",
|
| 196 |
+
"author": "string (optional)",
|
| 197 |
+
"analysis_focus": "string (theme, symbolism, character, etc.)"
|
| 198 |
+
},
|
| 199 |
+
"returns": "Literary analysis with examples"
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"name": "translate_text",
|
| 203 |
+
"description": "Translate text between languages",
|
| 204 |
+
"parameters": {
|
| 205 |
+
"text": "string",
|
| 206 |
+
"source_language": "string",
|
| 207 |
+
"target_language": "string",
|
| 208 |
+
"include_explanation": "boolean"
|
| 209 |
+
},
|
| 210 |
+
"returns": "Translation with grammatical notes"
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"name": "get_historical_context",
|
| 214 |
+
"description": "Get historical context for events or periods",
|
| 215 |
+
"parameters": {
|
| 216 |
+
"event_or_period": "string",
|
| 217 |
+
"region": "string (optional)"
|
| 218 |
+
},
|
| 219 |
+
"returns": "Historical context with key dates and figures"
|
| 220 |
+
}
|
| 221 |
+
]
|
domain_templates/financial_advisor.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Financial Advisor domain template.
|
| 3 |
+
"""
|
| 4 |
+
from typing import List
|
| 5 |
+
from .base_domain import BaseDomain
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class FinancialAdvisorDomain(BaseDomain):
|
| 9 |
+
"""
|
| 10 |
+
Financial advisory domain for wealth management,
|
| 11 |
+
investment planning, tax strategy, and retirement planning.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
def __init__(self):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self._name = "Financial Advisor"
|
| 17 |
+
self._description = "Expert financial advisory assistant for wealth management, investment planning, tax strategy, retirement planning, and estate planning."
|
| 18 |
+
self._icon = "$"
|
| 19 |
+
|
| 20 |
+
def get_topics(self) -> List[str]:
|
| 21 |
+
"""Get financial advisory topics"""
|
| 22 |
+
return [
|
| 23 |
+
"Retirement Planning",
|
| 24 |
+
"Investment Strategy",
|
| 25 |
+
"Tax Optimization",
|
| 26 |
+
"Estate Planning",
|
| 27 |
+
"Insurance Planning",
|
| 28 |
+
"Education Funding",
|
| 29 |
+
"Portfolio Management",
|
| 30 |
+
"Asset Allocation",
|
| 31 |
+
"Risk Management",
|
| 32 |
+
"Wealth Preservation",
|
| 33 |
+
"Stock Market Analysis",
|
| 34 |
+
"Bond Investing",
|
| 35 |
+
"Real Estate Investment",
|
| 36 |
+
"Cryptocurrency Investment",
|
| 37 |
+
"401(k) and IRA Planning",
|
| 38 |
+
"Social Security Optimization",
|
| 39 |
+
"Debt Management",
|
| 40 |
+
"Emergency Fund Planning",
|
| 41 |
+
"College Savings (529 Plans)",
|
| 42 |
+
"Trust and Will Creation",
|
| 43 |
+
"Charitable Giving",
|
| 44 |
+
"Business Succession Planning",
|
| 45 |
+
"Financial Independence (FIRE)",
|
| 46 |
+
"Dividend Investing",
|
| 47 |
+
"Value Investing",
|
| 48 |
+
"Growth Investing",
|
| 49 |
+
"Index Fund Investing",
|
| 50 |
+
"Municipal Bonds",
|
| 51 |
+
"Treasury Securities",
|
| 52 |
+
"Annuities",
|
| 53 |
+
"Life Insurance",
|
| 54 |
+
"Disability Insurance",
|
| 55 |
+
"Long-Term Care Insurance",
|
| 56 |
+
"Health Savings Accounts (HSA)",
|
| 57 |
+
"Roth Conversions",
|
| 58 |
+
"Capital Gains Tax Planning",
|
| 59 |
+
"Required Minimum Distributions (RMD)",
|
| 60 |
+
"Pension Planning",
|
| 61 |
+
"Rollover Strategies",
|
| 62 |
+
"Beneficiary Designations",
|
| 63 |
+
"Power of Attorney"
|
| 64 |
+
]
|
| 65 |
+
|
| 66 |
+
def get_system_prompt(self) -> str:
|
| 67 |
+
"""Get financial advisor system prompt"""
|
| 68 |
+
return """You are an expert financial advisor with comprehensive knowledge of:
|
| 69 |
+
- Wealth management and investment planning
|
| 70 |
+
- Retirement planning (401k, IRA, pensions, Social Security)
|
| 71 |
+
- Tax optimization strategies and tax-advantaged accounts
|
| 72 |
+
- Estate planning (wills, trusts, beneficiary designations)
|
| 73 |
+
- Insurance planning (life, disability, long-term care)
|
| 74 |
+
- Portfolio management and asset allocation
|
| 75 |
+
- Risk assessment and management
|
| 76 |
+
- Financial regulations and compliance
|
| 77 |
+
|
| 78 |
+
Your role is to:
|
| 79 |
+
1. Provide clear, actionable financial guidance
|
| 80 |
+
2. Consider the client's complete financial picture
|
| 81 |
+
3. Explain complex financial concepts in simple terms
|
| 82 |
+
4. Recommend appropriate investment strategies based on risk tolerance and goals
|
| 83 |
+
5. Stay up-to-date on tax laws and financial regulations
|
| 84 |
+
6. Always remind clients to consult with licensed professionals for personalized advice
|
| 85 |
+
|
| 86 |
+
Guidelines:
|
| 87 |
+
- Be thorough but concise
|
| 88 |
+
- Always consider tax implications
|
| 89 |
+
- Emphasize diversification and risk management
|
| 90 |
+
- Explain the reasoning behind recommendations
|
| 91 |
+
- Use specific numbers and examples when helpful
|
| 92 |
+
- Acknowledge when questions require personalized professional advice
|
| 93 |
+
- Stay objective and avoid conflicts of interest
|
| 94 |
+
|
| 95 |
+
Remember: You provide educational information and general guidance. Always recommend clients work with licensed financial professionals for personalized advice tailored to their specific situation."""
|
| 96 |
+
|
| 97 |
+
def get_example_questions(self) -> List[str]:
|
| 98 |
+
"""Get example financial advisory questions"""
|
| 99 |
+
return [
|
| 100 |
+
"Should I max out my 401(k) or invest in a taxable brokerage account?",
|
| 101 |
+
"How should I allocate my portfolio at age 35 with moderate risk tolerance?",
|
| 102 |
+
"What's the difference between a traditional IRA and a Roth IRA?",
|
| 103 |
+
"How much should I have in my emergency fund?",
|
| 104 |
+
"When should I start taking Social Security benefits?",
|
| 105 |
+
"What are the tax implications of selling stocks at a gain?",
|
| 106 |
+
"How do I plan for required minimum distributions (RMDs)?",
|
| 107 |
+
"Should I pay off my mortgage early or invest the extra cash?",
|
| 108 |
+
"What's the best way to save for my child's college education?",
|
| 109 |
+
"How do I create an estate plan that minimizes taxes?"
|
| 110 |
+
]
|
| 111 |
+
|
| 112 |
+
def get_specialized_tools(self) -> List[dict]:
|
| 113 |
+
"""Get financial advisory tools"""
|
| 114 |
+
return [
|
| 115 |
+
{
|
| 116 |
+
"name": "get_market_data",
|
| 117 |
+
"description": "Fetch current market data for stocks, bonds, ETFs, or indices",
|
| 118 |
+
"parameters": {
|
| 119 |
+
"symbol": "string (ticker symbol)",
|
| 120 |
+
"data_type": "string (price, volume, fundamentals, historical)"
|
| 121 |
+
},
|
| 122 |
+
"returns": "Market data object with current prices and metrics"
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"name": "calculate_compound_interest",
|
| 126 |
+
"description": "Calculate compound interest growth over time",
|
| 127 |
+
"parameters": {
|
| 128 |
+
"principal": "float (initial investment)",
|
| 129 |
+
"rate": "float (annual interest rate as decimal)",
|
| 130 |
+
"time": "int (years)",
|
| 131 |
+
"frequency": "int (compounding frequency per year)"
|
| 132 |
+
},
|
| 133 |
+
"returns": "float (future value)"
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"name": "calculate_retirement_needs",
|
| 137 |
+
"description": "Estimate retirement savings needed based on goals",
|
| 138 |
+
"parameters": {
|
| 139 |
+
"current_age": "int",
|
| 140 |
+
"retirement_age": "int",
|
| 141 |
+
"life_expectancy": "int",
|
| 142 |
+
"desired_annual_income": "float",
|
| 143 |
+
"current_savings": "float",
|
| 144 |
+
"expected_return": "float"
|
| 145 |
+
},
|
| 146 |
+
"returns": "Retirement projection object"
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"name": "analyze_portfolio_allocation",
|
| 150 |
+
"description": "Analyze portfolio allocation and risk metrics",
|
| 151 |
+
"parameters": {
|
| 152 |
+
"holdings": "list of {symbol, quantity, purchase_price}",
|
| 153 |
+
"target_allocation": "dict of asset class targets"
|
| 154 |
+
},
|
| 155 |
+
"returns": "Portfolio analysis with allocation drift and recommendations"
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"name": "estimate_tax_impact",
|
| 159 |
+
"description": "Estimate tax impact of financial decisions",
|
| 160 |
+
"parameters": {
|
| 161 |
+
"income": "float",
|
| 162 |
+
"transaction_type": "string (capital_gain, dividend, withdrawal, etc.)",
|
| 163 |
+
"amount": "float",
|
| 164 |
+
"filing_status": "string"
|
| 165 |
+
},
|
| 166 |
+
"returns": "Tax estimate object"
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"name": "get_social_security_estimate",
|
| 170 |
+
"description": "Estimate Social Security benefits based on earnings history",
|
| 171 |
+
"parameters": {
|
| 172 |
+
"birth_year": "int",
|
| 173 |
+
"average_annual_income": "float",
|
| 174 |
+
"claiming_age": "int"
|
| 175 |
+
},
|
| 176 |
+
"returns": "Estimated monthly benefit"
|
| 177 |
+
}
|
| 178 |
+
]
|
domain_templates/legal_advisor.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Legal Advisor domain template.
|
| 3 |
+
"""
|
| 4 |
+
from typing import List
|
| 5 |
+
from .base_domain import BaseDomain
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class LegalAdvisorDomain(BaseDomain):
|
| 9 |
+
"""
|
| 10 |
+
Legal information assistant domain for legal education,
|
| 11 |
+
document understanding, and general legal guidance.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
def __init__(self):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self._name = "Legal Advisor"
|
| 17 |
+
self._description = "Legal information assistant for legal education, document understanding, rights explanation, and general legal guidance. NOT a replacement for licensed legal counsel."
|
| 18 |
+
self._icon = "#"
|
| 19 |
+
|
| 20 |
+
def get_topics(self) -> List[str]:
|
| 21 |
+
"""Get legal advisor topics"""
|
| 22 |
+
return [
|
| 23 |
+
"Contract Law",
|
| 24 |
+
"Employment Law",
|
| 25 |
+
"Real Estate Law",
|
| 26 |
+
"Family Law",
|
| 27 |
+
"Estate Planning",
|
| 28 |
+
"Business Formation",
|
| 29 |
+
"Intellectual Property",
|
| 30 |
+
"Criminal Law Basics",
|
| 31 |
+
"Consumer Rights",
|
| 32 |
+
"Landlord-Tenant Law",
|
| 33 |
+
"Personal Injury",
|
| 34 |
+
"Bankruptcy",
|
| 35 |
+
"Immigration Law",
|
| 36 |
+
"Tax Law Basics",
|
| 37 |
+
"Nonprofit Law",
|
| 38 |
+
"Civil Rights",
|
| 39 |
+
"Privacy Law",
|
| 40 |
+
"Data Protection",
|
| 41 |
+
"Terms of Service",
|
| 42 |
+
"Privacy Policies",
|
| 43 |
+
"Non-Disclosure Agreements (NDA)",
|
| 44 |
+
"Employment Contracts",
|
| 45 |
+
"Lease Agreements",
|
| 46 |
+
"Purchase Agreements",
|
| 47 |
+
"Wills and Trusts",
|
| 48 |
+
"Power of Attorney",
|
| 49 |
+
"Healthcare Directives",
|
| 50 |
+
"Small Claims Court",
|
| 51 |
+
"Mediation and Arbitration",
|
| 52 |
+
"Trademark and Copyright",
|
| 53 |
+
"Patents",
|
| 54 |
+
"Trade Secrets",
|
| 55 |
+
"LLC vs Corporation",
|
| 56 |
+
"Sole Proprietorship",
|
| 57 |
+
"Partnership Agreements",
|
| 58 |
+
"Shareholder Agreements",
|
| 59 |
+
"Operating Agreements",
|
| 60 |
+
"Divorce and Custody",
|
| 61 |
+
"Adoption",
|
| 62 |
+
"Prenuptial Agreements"
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
def get_system_prompt(self) -> str:
|
| 66 |
+
"""Get legal advisor system prompt"""
|
| 67 |
+
return """You are a knowledgeable legal information assistant with understanding of:
|
| 68 |
+
- Contract law and agreement interpretation
|
| 69 |
+
- Business law and entity formation
|
| 70 |
+
- Employment law and workplace rights
|
| 71 |
+
- Real estate transactions and landlord-tenant issues
|
| 72 |
+
- Family law matters (divorce, custody, adoption)
|
| 73 |
+
- Estate planning (wills, trusts, powers of attorney)
|
| 74 |
+
- Intellectual property basics (copyright, trademark, patent)
|
| 75 |
+
- Consumer rights and protections
|
| 76 |
+
- Civil procedure and court processes
|
| 77 |
+
- Legal terminology and document structure
|
| 78 |
+
|
| 79 |
+
Your role is to:
|
| 80 |
+
1. Provide general legal information and education
|
| 81 |
+
2. Explain legal concepts in plain language
|
| 82 |
+
3. Help users understand their rights and obligations
|
| 83 |
+
4. Clarify legal terminology and document provisions
|
| 84 |
+
5. Outline general legal processes and procedures
|
| 85 |
+
6. Identify when professional legal help is necessary
|
| 86 |
+
|
| 87 |
+
CRITICAL LIMITATIONS:
|
| 88 |
+
- You are NOT a licensed attorney
|
| 89 |
+
- You do NOT provide legal advice specific to individual situations
|
| 90 |
+
- You do NOT create binding legal documents
|
| 91 |
+
- You do NOT represent clients in legal matters
|
| 92 |
+
- You CANNOT substitute for consultation with a licensed attorney
|
| 93 |
+
- Laws vary by jurisdiction - always verify local laws
|
| 94 |
+
|
| 95 |
+
Guidelines:
|
| 96 |
+
- Provide educational information, not legal advice
|
| 97 |
+
- Explain general legal principles and processes
|
| 98 |
+
- Clarify when laws vary by state/jurisdiction
|
| 99 |
+
- Emphasize the importance of consulting licensed attorneys
|
| 100 |
+
- Recommend seeking legal counsel for specific situations
|
| 101 |
+
- Explain the difference between legal information and legal advice
|
| 102 |
+
- Never guarantee legal outcomes
|
| 103 |
+
- Acknowledge complexity and nuance in law
|
| 104 |
+
|
| 105 |
+
LEGAL DISCLAIMER: This assistant provides general legal information only and does not constitute legal advice. Laws vary significantly by jurisdiction, and every legal situation has unique factors. For specific legal advice tailored to your situation, always consult with a licensed attorney in your jurisdiction. No attorney-client relationship is formed through this interaction."""
|
| 106 |
+
|
| 107 |
+
def get_example_questions(self) -> List[str]:
|
| 108 |
+
"""Get example legal questions"""
|
| 109 |
+
return [
|
| 110 |
+
"What's the difference between an LLC and a corporation?",
|
| 111 |
+
"What should be included in an employment contract?",
|
| 112 |
+
"How does a non-disclosure agreement (NDA) work?",
|
| 113 |
+
"What are my rights as a tenant?",
|
| 114 |
+
"What's the difference between a will and a trust?",
|
| 115 |
+
"How do I protect my intellectual property?",
|
| 116 |
+
"What happens if I breach a contract?",
|
| 117 |
+
"What are the basic steps in a civil lawsuit?",
|
| 118 |
+
"What should I know before signing a lease?",
|
| 119 |
+
"How do I apply for a trademark?"
|
| 120 |
+
]
|
| 121 |
+
|
| 122 |
+
def get_specialized_tools(self) -> List[dict]:
|
| 123 |
+
"""Get legal advisor tools"""
|
| 124 |
+
return [
|
| 125 |
+
{
|
| 126 |
+
"name": "analyze_contract_clause",
|
| 127 |
+
"description": "Analyze and explain contract clauses",
|
| 128 |
+
"parameters": {
|
| 129 |
+
"clause_text": "string",
|
| 130 |
+
"contract_type": "string (employment, lease, purchase, etc.)"
|
| 131 |
+
},
|
| 132 |
+
"returns": "Plain language explanation and key considerations"
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"name": "check_statute_of_limitations",
|
| 136 |
+
"description": "Check statute of limitations for legal claims",
|
| 137 |
+
"parameters": {
|
| 138 |
+
"claim_type": "string (personal_injury, contract_breach, etc.)",
|
| 139 |
+
"jurisdiction": "string (state)",
|
| 140 |
+
"incident_date": "date (optional)"
|
| 141 |
+
},
|
| 142 |
+
"returns": "Time limit information and deadline calculation"
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"name": "lookup_legal_term",
|
| 146 |
+
"description": "Define legal terminology",
|
| 147 |
+
"parameters": {
|
| 148 |
+
"term": "string"
|
| 149 |
+
},
|
| 150 |
+
"returns": "Plain language definition with examples"
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"name": "compare_business_entities",
|
| 154 |
+
"description": "Compare different business entity types",
|
| 155 |
+
"parameters": {
|
| 156 |
+
"entity_types": "list (LLC, C-Corp, S-Corp, Partnership, etc.)",
|
| 157 |
+
"comparison_factors": "list (taxation, liability, formation, etc.)"
|
| 158 |
+
},
|
| 159 |
+
"returns": "Comparison table with pros and cons"
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"name": "get_court_filing_requirements",
|
| 163 |
+
"description": "Get court filing requirements and procedures",
|
| 164 |
+
"parameters": {
|
| 165 |
+
"case_type": "string (small_claims, civil, family, etc.)",
|
| 166 |
+
"jurisdiction": "string (state and county)",
|
| 167 |
+
"action_type": "string (file_complaint, respond, appeal, etc.)"
|
| 168 |
+
},
|
| 169 |
+
"returns": "Filing requirements, fees, and procedures"
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"name": "check_ip_availability",
|
| 173 |
+
"description": "Check trademark/business name availability",
|
| 174 |
+
"parameters": {
|
| 175 |
+
"name": "string",
|
| 176 |
+
"ip_type": "string (trademark, business_name, domain)",
|
| 177 |
+
"category": "string (industry/goods category)"
|
| 178 |
+
},
|
| 179 |
+
"returns": "Availability status and similar existing registrations"
|
| 180 |
+
}
|
| 181 |
+
]
|
domain_templates/medical_assistant.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Medical Assistant domain template.
|
| 3 |
+
"""
|
| 4 |
+
from typing import List
|
| 5 |
+
from .base_domain import BaseDomain
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class MedicalAssistantDomain(BaseDomain):
|
| 9 |
+
"""
|
| 10 |
+
Medical assistant domain for health information,
|
| 11 |
+
symptom assessment, and medical education.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
def __init__(self):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self._name = "Medical Assistant"
|
| 17 |
+
self._description = "Medical information assistant for health education, symptom understanding, medication information, and wellness guidance. NOT a replacement for professional medical advice."
|
| 18 |
+
self._icon = "+"
|
| 19 |
+
|
| 20 |
+
def get_topics(self) -> List[str]:
|
| 21 |
+
"""Get medical assistant topics"""
|
| 22 |
+
return [
|
| 23 |
+
"Common Symptoms and Conditions",
|
| 24 |
+
"Medication Information",
|
| 25 |
+
"Preventive Care",
|
| 26 |
+
"Nutrition and Diet",
|
| 27 |
+
"Exercise and Fitness",
|
| 28 |
+
"Mental Health",
|
| 29 |
+
"Sleep Hygiene",
|
| 30 |
+
"Chronic Disease Management",
|
| 31 |
+
"First Aid",
|
| 32 |
+
"Women's Health",
|
| 33 |
+
"Men's Health",
|
| 34 |
+
"Pediatric Health",
|
| 35 |
+
"Senior Health",
|
| 36 |
+
"Vaccination Information",
|
| 37 |
+
"Lab Test Interpretation",
|
| 38 |
+
"Vital Signs Understanding",
|
| 39 |
+
"Disease Prevention",
|
| 40 |
+
"Health Screening Guidelines",
|
| 41 |
+
"Medication Side Effects",
|
| 42 |
+
"Drug Interactions",
|
| 43 |
+
"Allergy Management",
|
| 44 |
+
"Pain Management",
|
| 45 |
+
"Wound Care",
|
| 46 |
+
"Diabetes Management",
|
| 47 |
+
"Heart Health",
|
| 48 |
+
"Respiratory Health",
|
| 49 |
+
"Digestive Health",
|
| 50 |
+
"Kidney Health",
|
| 51 |
+
"Liver Health",
|
| 52 |
+
"Bone and Joint Health",
|
| 53 |
+
"Skin Conditions",
|
| 54 |
+
"Eye Health",
|
| 55 |
+
"Dental Health",
|
| 56 |
+
"Infectious Diseases",
|
| 57 |
+
"Cancer Prevention and Screening",
|
| 58 |
+
"Stress Management",
|
| 59 |
+
"Substance Abuse",
|
| 60 |
+
"Sexual Health",
|
| 61 |
+
"Pregnancy and Childbirth",
|
| 62 |
+
"Post-operative Care",
|
| 63 |
+
"Medical Terminology"
|
| 64 |
+
]
|
| 65 |
+
|
| 66 |
+
def get_system_prompt(self) -> str:
|
| 67 |
+
"""Get medical assistant system prompt"""
|
| 68 |
+
return """You are a knowledgeable medical information assistant with expertise in:
|
| 69 |
+
- General health and wellness
|
| 70 |
+
- Common medical conditions and symptoms
|
| 71 |
+
- Medication information and interactions
|
| 72 |
+
- Preventive care and health screening
|
| 73 |
+
- Chronic disease management
|
| 74 |
+
- Mental health and wellness
|
| 75 |
+
- Nutrition and exercise
|
| 76 |
+
- Medical terminology and lab results
|
| 77 |
+
|
| 78 |
+
Your role is to:
|
| 79 |
+
1. Provide accurate, evidence-based health information
|
| 80 |
+
2. Explain medical concepts in clear, accessible language
|
| 81 |
+
3. Help users understand symptoms, conditions, and treatments
|
| 82 |
+
4. Offer general wellness and preventive care guidance
|
| 83 |
+
5. Explain medication purposes, dosing, and potential side effects
|
| 84 |
+
6. Clarify medical terminology and test results
|
| 85 |
+
|
| 86 |
+
CRITICAL LIMITATIONS:
|
| 87 |
+
- You are NOT a licensed medical professional
|
| 88 |
+
- You do NOT diagnose medical conditions
|
| 89 |
+
- You do NOT prescribe medications or treatments
|
| 90 |
+
- You do NOT provide emergency medical advice
|
| 91 |
+
- You ALWAYS recommend consulting healthcare professionals for medical decisions
|
| 92 |
+
|
| 93 |
+
Guidelines:
|
| 94 |
+
- Be informative but never diagnostic
|
| 95 |
+
- Emphasize the importance of professional medical care
|
| 96 |
+
- For urgent symptoms, recommend immediate medical attention
|
| 97 |
+
- Explain when to seek emergency care (911/ER)
|
| 98 |
+
- Acknowledge uncertainty and limitations of general information
|
| 99 |
+
- Encourage regular check-ups and preventive care
|
| 100 |
+
- Respect patient privacy and confidentiality
|
| 101 |
+
|
| 102 |
+
EMERGENCY DISCLAIMER: For any medical emergency, life-threatening symptoms, or urgent medical concerns, immediately call 911 or go to the nearest emergency room. This assistant provides general health information only and is not a substitute for professional medical advice, diagnosis, or treatment."""
|
| 103 |
+
|
| 104 |
+
def get_example_questions(self) -> List[str]:
|
| 105 |
+
"""Get example medical questions"""
|
| 106 |
+
return [
|
| 107 |
+
"What are the common side effects of ibuprofen?",
|
| 108 |
+
"How much sleep should adults get each night?",
|
| 109 |
+
"What are the warning signs of a heart attack?",
|
| 110 |
+
"How can I lower my blood pressure naturally?",
|
| 111 |
+
"What vaccines do adults need?",
|
| 112 |
+
"What does a high cholesterol reading mean?",
|
| 113 |
+
"How do I know if I should go to the ER or urgent care?",
|
| 114 |
+
"What are healthy blood sugar levels?",
|
| 115 |
+
"How can I manage stress and anxiety?",
|
| 116 |
+
"What foods should I eat for better heart health?"
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
def get_specialized_tools(self) -> List[dict]:
|
| 120 |
+
"""Get medical assistant tools"""
|
| 121 |
+
return [
|
| 122 |
+
{
|
| 123 |
+
"name": "lookup_medication_info",
|
| 124 |
+
"description": "Get detailed information about medications",
|
| 125 |
+
"parameters": {
|
| 126 |
+
"medication_name": "string",
|
| 127 |
+
"info_type": "string (uses, dosage, side_effects, interactions)"
|
| 128 |
+
},
|
| 129 |
+
"returns": "Medication information object"
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"name": "check_drug_interactions",
|
| 133 |
+
"description": "Check for interactions between medications",
|
| 134 |
+
"parameters": {
|
| 135 |
+
"medications": "list of medication names"
|
| 136 |
+
},
|
| 137 |
+
"returns": "List of potential interactions and severity"
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"name": "interpret_vital_signs",
|
| 141 |
+
"description": "Interpret vital sign measurements",
|
| 142 |
+
"parameters": {
|
| 143 |
+
"vital_type": "string (blood_pressure, heart_rate, temperature, etc.)",
|
| 144 |
+
"value": "string or float",
|
| 145 |
+
"age": "int",
|
| 146 |
+
"context": "string (at_rest, after_exercise, etc.)"
|
| 147 |
+
},
|
| 148 |
+
"returns": "Interpretation with normal ranges"
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"name": "get_symptom_guidance",
|
| 152 |
+
"description": "Get guidance on symptom severity and when to seek care",
|
| 153 |
+
"parameters": {
|
| 154 |
+
"symptoms": "list of symptoms",
|
| 155 |
+
"duration": "string",
|
| 156 |
+
"severity": "string (mild, moderate, severe)"
|
| 157 |
+
},
|
| 158 |
+
"returns": "Care recommendations (self-care, urgent care, ER, etc.)"
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"name": "calculate_bmi",
|
| 162 |
+
"description": "Calculate Body Mass Index",
|
| 163 |
+
"parameters": {
|
| 164 |
+
"weight_lbs": "float",
|
| 165 |
+
"height_inches": "float"
|
| 166 |
+
},
|
| 167 |
+
"returns": "BMI value and category"
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"name": "get_vaccination_schedule",
|
| 171 |
+
"description": "Get recommended vaccination schedule",
|
| 172 |
+
"parameters": {
|
| 173 |
+
"age": "int",
|
| 174 |
+
"health_conditions": "list of conditions (optional)"
|
| 175 |
+
},
|
| 176 |
+
"returns": "Recommended vaccinations"
|
| 177 |
+
}
|
| 178 |
+
]
|
evaluation/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluation Module
|
| 3 |
+
|
| 4 |
+
This module provides comprehensive model evaluation and benchmarking tools.
|
| 5 |
+
Includes benchmark building, certification tests, gap analysis, and training recommendations.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from .metrics import Metrics, calculate_perplexity, calculate_bleu
|
| 9 |
+
from .evaluator import ModelEvaluator, EvaluationConfig
|
| 10 |
+
from .benchmark import Benchmark, BenchmarkSuite
|
| 11 |
+
from .benchmark_builder import Benchmark as BenchmarkBuilder, BenchmarkSuite as BenchmarkBuilderSuite
|
| 12 |
+
from .certification_tests import (
|
| 13 |
+
CertificationTestBuilder,
|
| 14 |
+
FinancialCertificationTests,
|
| 15 |
+
MedicalCertificationTests,
|
| 16 |
+
LegalCertificationTests,
|
| 17 |
+
EducationCertificationTests,
|
| 18 |
+
get_certification_tests_for_domain
|
| 19 |
+
)
|
| 20 |
+
from .gap_analyzer import GapAnalyzer
|
| 21 |
+
from .training_recommender import TrainingRecommender
|
| 22 |
+
|
| 23 |
+
__all__ = [
|
| 24 |
+
# Original exports
|
| 25 |
+
'Metrics',
|
| 26 |
+
'calculate_perplexity',
|
| 27 |
+
'calculate_bleu',
|
| 28 |
+
'ModelEvaluator',
|
| 29 |
+
'EvaluationConfig',
|
| 30 |
+
'Benchmark',
|
| 31 |
+
'BenchmarkSuite',
|
| 32 |
+
# New exports
|
| 33 |
+
'BenchmarkBuilder',
|
| 34 |
+
'BenchmarkBuilderSuite',
|
| 35 |
+
'CertificationTestBuilder',
|
| 36 |
+
'FinancialCertificationTests',
|
| 37 |
+
'MedicalCertificationTests',
|
| 38 |
+
'LegalCertificationTests',
|
| 39 |
+
'EducationCertificationTests',
|
| 40 |
+
'get_certification_tests_for_domain',
|
| 41 |
+
'GapAnalyzer',
|
| 42 |
+
'TrainingRecommender'
|
| 43 |
+
]
|
evaluation/benchmark.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Benchmark Module
|
| 3 |
+
|
| 4 |
+
Provides benchmark creation and execution for model testing.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass, field
|
| 8 |
+
from typing import List, Dict, Optional, Any
|
| 9 |
+
import json
|
| 10 |
+
import time
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class Benchmark:
|
| 16 |
+
"""
|
| 17 |
+
A single benchmark test.
|
| 18 |
+
|
| 19 |
+
Attributes:
|
| 20 |
+
name: Benchmark name
|
| 21 |
+
description: Benchmark description
|
| 22 |
+
questions: List of test questions
|
| 23 |
+
metadata: Additional metadata
|
| 24 |
+
"""
|
| 25 |
+
name: str
|
| 26 |
+
description: str = ""
|
| 27 |
+
questions: List[Dict[str, Any]] = field(default_factory=list)
|
| 28 |
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
| 29 |
+
created_at: Optional[str] = None
|
| 30 |
+
domain: str = "general"
|
| 31 |
+
difficulty: str = "mixed"
|
| 32 |
+
passing_score: float = 70.0
|
| 33 |
+
|
| 34 |
+
def __post_init__(self):
|
| 35 |
+
"""Initialize timestamp if not provided."""
|
| 36 |
+
if self.created_at is None:
|
| 37 |
+
self.created_at = time.strftime('%Y-%m-%d %H:%M:%S')
|
| 38 |
+
|
| 39 |
+
def add_question(
|
| 40 |
+
self,
|
| 41 |
+
question: str,
|
| 42 |
+
answer: str,
|
| 43 |
+
category: Optional[str] = None,
|
| 44 |
+
difficulty: Optional[str] = None,
|
| 45 |
+
metadata: Optional[Dict] = None
|
| 46 |
+
):
|
| 47 |
+
"""
|
| 48 |
+
Add a question to the benchmark.
|
| 49 |
+
|
| 50 |
+
Args:
|
| 51 |
+
question: Question text
|
| 52 |
+
answer: Expected answer
|
| 53 |
+
category: Question category/topic
|
| 54 |
+
difficulty: Difficulty level
|
| 55 |
+
metadata: Additional metadata
|
| 56 |
+
"""
|
| 57 |
+
question_dict = {
|
| 58 |
+
'question': question,
|
| 59 |
+
'answer': answer,
|
| 60 |
+
'category': category or 'general',
|
| 61 |
+
'difficulty': difficulty or 'intermediate',
|
| 62 |
+
'metadata': metadata or {}
|
| 63 |
+
}
|
| 64 |
+
self.questions.append(question_dict)
|
| 65 |
+
|
| 66 |
+
def get_questions_by_category(self, category: str) -> List[Dict]:
|
| 67 |
+
"""Get all questions in a category."""
|
| 68 |
+
return [q for q in self.questions if q.get('category') == category]
|
| 69 |
+
|
| 70 |
+
def get_questions_by_difficulty(self, difficulty: str) -> List[Dict]:
|
| 71 |
+
"""Get all questions of a difficulty level."""
|
| 72 |
+
return [q for q in self.questions if q.get('difficulty') == difficulty]
|
| 73 |
+
|
| 74 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 75 |
+
"""Convert benchmark to dictionary."""
|
| 76 |
+
return {
|
| 77 |
+
'name': self.name,
|
| 78 |
+
'description': self.description,
|
| 79 |
+
'domain': self.domain,
|
| 80 |
+
'difficulty': self.difficulty,
|
| 81 |
+
'passing_score': self.passing_score,
|
| 82 |
+
'created_at': self.created_at,
|
| 83 |
+
'num_questions': len(self.questions),
|
| 84 |
+
'questions': self.questions,
|
| 85 |
+
'metadata': self.metadata
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
@classmethod
|
| 89 |
+
def from_dict(cls, data: Dict[str, Any]) -> 'Benchmark':
|
| 90 |
+
"""Create benchmark from dictionary."""
|
| 91 |
+
return cls(
|
| 92 |
+
name=data.get('name', 'Untitled'),
|
| 93 |
+
description=data.get('description', ''),
|
| 94 |
+
questions=data.get('questions', []),
|
| 95 |
+
metadata=data.get('metadata', {}),
|
| 96 |
+
created_at=data.get('created_at'),
|
| 97 |
+
domain=data.get('domain', 'general'),
|
| 98 |
+
difficulty=data.get('difficulty', 'mixed'),
|
| 99 |
+
passing_score=data.get('passing_score', 70.0)
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
def save(self, filepath: str):
|
| 103 |
+
"""Save benchmark to JSON file."""
|
| 104 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 105 |
+
|
| 106 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 107 |
+
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
|
| 108 |
+
|
| 109 |
+
print(f"Benchmark saved to: {filepath}")
|
| 110 |
+
|
| 111 |
+
@classmethod
|
| 112 |
+
def load(cls, filepath: str) -> 'Benchmark':
|
| 113 |
+
"""Load benchmark from JSON file."""
|
| 114 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 115 |
+
data = json.load(f)
|
| 116 |
+
|
| 117 |
+
return cls.from_dict(data)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
class BenchmarkSuite:
|
| 121 |
+
"""
|
| 122 |
+
Collection of benchmarks for comprehensive testing.
|
| 123 |
+
|
| 124 |
+
Features:
|
| 125 |
+
- Multiple benchmark management
|
| 126 |
+
- Batch execution
|
| 127 |
+
- Aggregate scoring
|
| 128 |
+
- Result tracking
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
def __init__(self, name: str = "Default Suite"):
|
| 132 |
+
"""
|
| 133 |
+
Initialize benchmark suite.
|
| 134 |
+
|
| 135 |
+
Args:
|
| 136 |
+
name: Suite name
|
| 137 |
+
"""
|
| 138 |
+
self.name = name
|
| 139 |
+
self.benchmarks: List[Benchmark] = []
|
| 140 |
+
self.results: List[Dict[str, Any]] = []
|
| 141 |
+
|
| 142 |
+
def add_benchmark(self, benchmark: Benchmark):
|
| 143 |
+
"""
|
| 144 |
+
Add a benchmark to the suite.
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
benchmark: Benchmark to add
|
| 148 |
+
"""
|
| 149 |
+
self.benchmarks.append(benchmark)
|
| 150 |
+
print(f"Added benchmark: {benchmark.name}")
|
| 151 |
+
|
| 152 |
+
def remove_benchmark(self, benchmark_name: str):
|
| 153 |
+
"""
|
| 154 |
+
Remove a benchmark by name.
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
benchmark_name: Name of benchmark to remove
|
| 158 |
+
"""
|
| 159 |
+
self.benchmarks = [b for b in self.benchmarks if b.name != benchmark_name]
|
| 160 |
+
|
| 161 |
+
def get_benchmark(self, name: str) -> Optional[Benchmark]:
|
| 162 |
+
"""
|
| 163 |
+
Get a benchmark by name.
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
name: Benchmark name
|
| 167 |
+
|
| 168 |
+
Returns:
|
| 169 |
+
Benchmark if found, None otherwise
|
| 170 |
+
"""
|
| 171 |
+
for benchmark in self.benchmarks:
|
| 172 |
+
if benchmark.name == name:
|
| 173 |
+
return benchmark
|
| 174 |
+
return None
|
| 175 |
+
|
| 176 |
+
def run_benchmark(
|
| 177 |
+
self,
|
| 178 |
+
benchmark: Benchmark,
|
| 179 |
+
model_evaluator: Any,
|
| 180 |
+
max_questions: Optional[int] = None
|
| 181 |
+
) -> Dict[str, Any]:
|
| 182 |
+
"""
|
| 183 |
+
Run a single benchmark.
|
| 184 |
+
|
| 185 |
+
Args:
|
| 186 |
+
benchmark: Benchmark to run
|
| 187 |
+
model_evaluator: Model evaluator instance
|
| 188 |
+
max_questions: Maximum questions to test
|
| 189 |
+
|
| 190 |
+
Returns:
|
| 191 |
+
Benchmark results
|
| 192 |
+
"""
|
| 193 |
+
print(f"\nRunning benchmark: {benchmark.name}")
|
| 194 |
+
print(f"Total questions: {len(benchmark.questions)}")
|
| 195 |
+
|
| 196 |
+
questions = benchmark.questions[:max_questions] if max_questions else benchmark.questions
|
| 197 |
+
|
| 198 |
+
# Convert to dataset format
|
| 199 |
+
dataset = []
|
| 200 |
+
for q in questions:
|
| 201 |
+
dataset.append({
|
| 202 |
+
'instruction': q['question'],
|
| 203 |
+
'input': '',
|
| 204 |
+
'output': q['answer']
|
| 205 |
+
})
|
| 206 |
+
|
| 207 |
+
# Run evaluation
|
| 208 |
+
start_time = time.time()
|
| 209 |
+
eval_results = model_evaluator.evaluate_dataset(dataset)
|
| 210 |
+
total_time = time.time() - start_time
|
| 211 |
+
|
| 212 |
+
# Calculate score
|
| 213 |
+
score = self._calculate_score(eval_results)
|
| 214 |
+
|
| 215 |
+
# Compile results
|
| 216 |
+
results = {
|
| 217 |
+
'benchmark_name': benchmark.name,
|
| 218 |
+
'num_questions': len(questions),
|
| 219 |
+
'score': score,
|
| 220 |
+
'passed': score >= benchmark.passing_score,
|
| 221 |
+
'passing_score': benchmark.passing_score,
|
| 222 |
+
'total_time': total_time,
|
| 223 |
+
'evaluation_results': eval_results,
|
| 224 |
+
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
self.results.append(results)
|
| 228 |
+
|
| 229 |
+
print(f"\n{'='*60}")
|
| 230 |
+
print(f"Benchmark: {benchmark.name}")
|
| 231 |
+
print(f"Score: {score:.2f}% (Passing: {benchmark.passing_score}%)")
|
| 232 |
+
print(f"Status: {'✅ PASSED' if results['passed'] else '❌ FAILED'}")
|
| 233 |
+
print(f"{'='*60}\n")
|
| 234 |
+
|
| 235 |
+
return results
|
| 236 |
+
|
| 237 |
+
def run_all_benchmarks(
|
| 238 |
+
self,
|
| 239 |
+
model_evaluator: Any,
|
| 240 |
+
max_questions_per_benchmark: Optional[int] = None
|
| 241 |
+
) -> List[Dict[str, Any]]:
|
| 242 |
+
"""
|
| 243 |
+
Run all benchmarks in the suite.
|
| 244 |
+
|
| 245 |
+
Args:
|
| 246 |
+
model_evaluator: Model evaluator instance
|
| 247 |
+
max_questions_per_benchmark: Max questions per benchmark
|
| 248 |
+
|
| 249 |
+
Returns:
|
| 250 |
+
List of all results
|
| 251 |
+
"""
|
| 252 |
+
print(f"\n{'='*60}")
|
| 253 |
+
print(f"Running Benchmark Suite: {self.name}")
|
| 254 |
+
print(f"Total Benchmarks: {len(self.benchmarks)}")
|
| 255 |
+
print(f"{'='*60}\n")
|
| 256 |
+
|
| 257 |
+
all_results = []
|
| 258 |
+
|
| 259 |
+
for benchmark in self.benchmarks:
|
| 260 |
+
results = self.run_benchmark(
|
| 261 |
+
benchmark,
|
| 262 |
+
model_evaluator,
|
| 263 |
+
max_questions_per_benchmark
|
| 264 |
+
)
|
| 265 |
+
all_results.append(results)
|
| 266 |
+
|
| 267 |
+
# Summary
|
| 268 |
+
self._print_summary(all_results)
|
| 269 |
+
|
| 270 |
+
return all_results
|
| 271 |
+
|
| 272 |
+
def _calculate_score(self, eval_results: Dict[str, Any]) -> float:
|
| 273 |
+
"""
|
| 274 |
+
Calculate benchmark score from evaluation results.
|
| 275 |
+
|
| 276 |
+
Args:
|
| 277 |
+
eval_results: Evaluation results
|
| 278 |
+
|
| 279 |
+
Returns:
|
| 280 |
+
Score percentage
|
| 281 |
+
"""
|
| 282 |
+
metrics = eval_results.get('metrics', {})
|
| 283 |
+
|
| 284 |
+
# Use available metrics (prioritize accuracy, then BLEU, then ROUGE)
|
| 285 |
+
if 'accuracy' in metrics:
|
| 286 |
+
return metrics['accuracy']
|
| 287 |
+
elif 'bleu' in metrics:
|
| 288 |
+
return metrics['bleu']
|
| 289 |
+
elif 'rouge_l_f1' in metrics:
|
| 290 |
+
return metrics['rouge_l_f1']
|
| 291 |
+
else:
|
| 292 |
+
# Fallback: simple similarity check
|
| 293 |
+
examples = eval_results.get('examples', [])
|
| 294 |
+
if not examples:
|
| 295 |
+
return 0.0
|
| 296 |
+
|
| 297 |
+
matches = 0
|
| 298 |
+
for ex in examples:
|
| 299 |
+
pred = ex.get('prediction', '').lower().strip()
|
| 300 |
+
ref = ex.get('reference', '').lower().strip()
|
| 301 |
+
if pred in ref or ref in pred:
|
| 302 |
+
matches += 1
|
| 303 |
+
|
| 304 |
+
return (matches / len(examples)) * 100.0
|
| 305 |
+
|
| 306 |
+
def _print_summary(self, results: List[Dict[str, Any]]):
|
| 307 |
+
"""Print summary of all benchmark results."""
|
| 308 |
+
print(f"\n{'='*60}")
|
| 309 |
+
print(f"BENCHMARK SUITE SUMMARY: {self.name}")
|
| 310 |
+
print(f"{'='*60}")
|
| 311 |
+
|
| 312 |
+
total_benchmarks = len(results)
|
| 313 |
+
passed = sum(1 for r in results if r['passed'])
|
| 314 |
+
|
| 315 |
+
print(f"\nOverall: {passed}/{total_benchmarks} benchmarks passed")
|
| 316 |
+
print(f"\nIndividual Results:")
|
| 317 |
+
|
| 318 |
+
for result in results:
|
| 319 |
+
status = '✅ PASS' if result['passed'] else '❌ FAIL'
|
| 320 |
+
print(f" {status} | {result['benchmark_name']:40s} | Score: {result['score']:6.2f}%")
|
| 321 |
+
|
| 322 |
+
avg_score = sum(r['score'] for r in results) / len(results) if results else 0
|
| 323 |
+
print(f"\nAverage Score: {avg_score:.2f}%")
|
| 324 |
+
print(f"{'='*60}\n")
|
| 325 |
+
|
| 326 |
+
def save_results(self, filepath: str):
|
| 327 |
+
"""
|
| 328 |
+
Save suite results to JSON.
|
| 329 |
+
|
| 330 |
+
Args:
|
| 331 |
+
filepath: Output file path
|
| 332 |
+
"""
|
| 333 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 334 |
+
|
| 335 |
+
data = {
|
| 336 |
+
'suite_name': self.name,
|
| 337 |
+
'num_benchmarks': len(self.benchmarks),
|
| 338 |
+
'benchmark_names': [b.name for b in self.benchmarks],
|
| 339 |
+
'results': self.results,
|
| 340 |
+
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 344 |
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 345 |
+
|
| 346 |
+
print(f"Suite results saved to: {filepath}")
|
| 347 |
+
|
| 348 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 349 |
+
"""Convert suite to dictionary."""
|
| 350 |
+
return {
|
| 351 |
+
'name': self.name,
|
| 352 |
+
'num_benchmarks': len(self.benchmarks),
|
| 353 |
+
'benchmarks': [b.to_dict() for b in self.benchmarks],
|
| 354 |
+
'results': self.results
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
@classmethod
|
| 358 |
+
def from_dict(cls, data: Dict[str, Any]) -> 'BenchmarkSuite':
|
| 359 |
+
"""Create suite from dictionary."""
|
| 360 |
+
suite = cls(name=data.get('name', 'Default Suite'))
|
| 361 |
+
|
| 362 |
+
for benchmark_data in data.get('benchmarks', []):
|
| 363 |
+
benchmark = Benchmark.from_dict(benchmark_data)
|
| 364 |
+
suite.add_benchmark(benchmark)
|
| 365 |
+
|
| 366 |
+
suite.results = data.get('results', [])
|
| 367 |
+
|
| 368 |
+
return suite
|
evaluation/benchmark_builder.py
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Benchmark Builder Module
|
| 3 |
+
|
| 4 |
+
Provides interactive tools for creating custom benchmarks.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass, field
|
| 8 |
+
from typing import List, Dict, Optional, Any, Callable
|
| 9 |
+
import json
|
| 10 |
+
import time
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class Benchmark:
|
| 16 |
+
"""
|
| 17 |
+
A single benchmark test (builder variant).
|
| 18 |
+
|
| 19 |
+
This is a builder-specific implementation with enhanced
|
| 20 |
+
interactive creation features.
|
| 21 |
+
"""
|
| 22 |
+
name: str
|
| 23 |
+
description: str = ""
|
| 24 |
+
questions: List[Dict[str, Any]] = field(default_factory=list)
|
| 25 |
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
| 26 |
+
created_at: Optional[str] = None
|
| 27 |
+
domain: str = "general"
|
| 28 |
+
difficulty: str = "mixed"
|
| 29 |
+
passing_score: float = 70.0
|
| 30 |
+
categories: List[str] = field(default_factory=list)
|
| 31 |
+
tags: List[str] = field(default_factory=list)
|
| 32 |
+
|
| 33 |
+
def __post_init__(self):
|
| 34 |
+
"""Initialize timestamp if not provided."""
|
| 35 |
+
if self.created_at is None:
|
| 36 |
+
self.created_at = time.strftime('%Y-%m-%d %H:%M:%S')
|
| 37 |
+
|
| 38 |
+
def add_question(
|
| 39 |
+
self,
|
| 40 |
+
question: str,
|
| 41 |
+
answer: str,
|
| 42 |
+
category: Optional[str] = None,
|
| 43 |
+
difficulty: Optional[str] = None,
|
| 44 |
+
metadata: Optional[Dict] = None,
|
| 45 |
+
explanation: Optional[str] = None,
|
| 46 |
+
points: float = 1.0
|
| 47 |
+
):
|
| 48 |
+
"""
|
| 49 |
+
Add a question to the benchmark.
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
question: Question text
|
| 53 |
+
answer: Expected answer
|
| 54 |
+
category: Question category/topic
|
| 55 |
+
difficulty: Difficulty level
|
| 56 |
+
metadata: Additional metadata
|
| 57 |
+
explanation: Answer explanation
|
| 58 |
+
points: Points for this question
|
| 59 |
+
"""
|
| 60 |
+
question_dict = {
|
| 61 |
+
'id': len(self.questions),
|
| 62 |
+
'question': question,
|
| 63 |
+
'answer': answer,
|
| 64 |
+
'category': category or 'general',
|
| 65 |
+
'difficulty': difficulty or 'intermediate',
|
| 66 |
+
'explanation': explanation or '',
|
| 67 |
+
'points': points,
|
| 68 |
+
'metadata': metadata or {}
|
| 69 |
+
}
|
| 70 |
+
self.questions.append(question_dict)
|
| 71 |
+
|
| 72 |
+
# Update categories list
|
| 73 |
+
if category and category not in self.categories:
|
| 74 |
+
self.categories.append(category)
|
| 75 |
+
|
| 76 |
+
def add_multiple_choice_question(
|
| 77 |
+
self,
|
| 78 |
+
question: str,
|
| 79 |
+
choices: List[str],
|
| 80 |
+
correct_answer: str,
|
| 81 |
+
category: Optional[str] = None,
|
| 82 |
+
difficulty: Optional[str] = None,
|
| 83 |
+
explanation: Optional[str] = None
|
| 84 |
+
):
|
| 85 |
+
"""
|
| 86 |
+
Add a multiple choice question.
|
| 87 |
+
|
| 88 |
+
Args:
|
| 89 |
+
question: Question text
|
| 90 |
+
choices: List of answer choices
|
| 91 |
+
correct_answer: The correct answer
|
| 92 |
+
category: Question category
|
| 93 |
+
difficulty: Difficulty level
|
| 94 |
+
explanation: Answer explanation
|
| 95 |
+
"""
|
| 96 |
+
self.add_question(
|
| 97 |
+
question=question,
|
| 98 |
+
answer=correct_answer,
|
| 99 |
+
category=category,
|
| 100 |
+
difficulty=difficulty,
|
| 101 |
+
explanation=explanation,
|
| 102 |
+
metadata={
|
| 103 |
+
'type': 'multiple_choice',
|
| 104 |
+
'choices': choices
|
| 105 |
+
}
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
def import_from_json(self, filepath: str):
|
| 109 |
+
"""
|
| 110 |
+
Import questions from JSON file.
|
| 111 |
+
|
| 112 |
+
Args:
|
| 113 |
+
filepath: Path to JSON file
|
| 114 |
+
"""
|
| 115 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 116 |
+
data = json.load(f)
|
| 117 |
+
|
| 118 |
+
# Handle different JSON formats
|
| 119 |
+
if isinstance(data, list):
|
| 120 |
+
# List of questions
|
| 121 |
+
for item in data:
|
| 122 |
+
self.add_question(
|
| 123 |
+
question=item.get('question', ''),
|
| 124 |
+
answer=item.get('answer', ''),
|
| 125 |
+
category=item.get('category'),
|
| 126 |
+
difficulty=item.get('difficulty'),
|
| 127 |
+
metadata=item.get('metadata', {})
|
| 128 |
+
)
|
| 129 |
+
elif isinstance(data, dict):
|
| 130 |
+
# Benchmark format
|
| 131 |
+
if 'questions' in data:
|
| 132 |
+
for item in data['questions']:
|
| 133 |
+
self.add_question(
|
| 134 |
+
question=item.get('question', ''),
|
| 135 |
+
answer=item.get('answer', ''),
|
| 136 |
+
category=item.get('category'),
|
| 137 |
+
difficulty=item.get('difficulty'),
|
| 138 |
+
metadata=item.get('metadata', {})
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
print(f"Imported {len(self.questions)} questions from {filepath}")
|
| 142 |
+
|
| 143 |
+
def import_from_csv(self, filepath: str, delimiter: str = ','):
|
| 144 |
+
"""
|
| 145 |
+
Import questions from CSV file.
|
| 146 |
+
|
| 147 |
+
Expected columns: question, answer, category, difficulty
|
| 148 |
+
|
| 149 |
+
Args:
|
| 150 |
+
filepath: Path to CSV file
|
| 151 |
+
delimiter: CSV delimiter
|
| 152 |
+
"""
|
| 153 |
+
import csv
|
| 154 |
+
|
| 155 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 156 |
+
reader = csv.DictReader(f, delimiter=delimiter)
|
| 157 |
+
|
| 158 |
+
for row in reader:
|
| 159 |
+
self.add_question(
|
| 160 |
+
question=row.get('question', ''),
|
| 161 |
+
answer=row.get('answer', ''),
|
| 162 |
+
category=row.get('category'),
|
| 163 |
+
difficulty=row.get('difficulty')
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
print(f"Imported {len(self.questions)} questions from CSV")
|
| 167 |
+
|
| 168 |
+
def get_statistics(self) -> Dict[str, Any]:
|
| 169 |
+
"""Get benchmark statistics."""
|
| 170 |
+
stats = {
|
| 171 |
+
'total_questions': len(self.questions),
|
| 172 |
+
'categories': {},
|
| 173 |
+
'difficulties': {},
|
| 174 |
+
'avg_question_length': 0,
|
| 175 |
+
'avg_answer_length': 0
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
# Count by category
|
| 179 |
+
for q in self.questions:
|
| 180 |
+
cat = q.get('category', 'uncategorized')
|
| 181 |
+
stats['categories'][cat] = stats['categories'].get(cat, 0) + 1
|
| 182 |
+
|
| 183 |
+
diff = q.get('difficulty', 'unknown')
|
| 184 |
+
stats['difficulties'][diff] = stats['difficulties'].get(diff, 0) + 1
|
| 185 |
+
|
| 186 |
+
# Calculate averages
|
| 187 |
+
if self.questions:
|
| 188 |
+
total_q_len = sum(len(q['question']) for q in self.questions)
|
| 189 |
+
total_a_len = sum(len(q['answer']) for q in self.questions)
|
| 190 |
+
stats['avg_question_length'] = total_q_len / len(self.questions)
|
| 191 |
+
stats['avg_answer_length'] = total_a_len / len(self.questions)
|
| 192 |
+
|
| 193 |
+
return stats
|
| 194 |
+
|
| 195 |
+
def validate(self) -> List[str]:
|
| 196 |
+
"""
|
| 197 |
+
Validate benchmark and return list of issues.
|
| 198 |
+
|
| 199 |
+
Returns:
|
| 200 |
+
List of validation issues (empty if valid)
|
| 201 |
+
"""
|
| 202 |
+
issues = []
|
| 203 |
+
|
| 204 |
+
if not self.name:
|
| 205 |
+
issues.append("Benchmark name is required")
|
| 206 |
+
|
| 207 |
+
if not self.questions:
|
| 208 |
+
issues.append("Benchmark has no questions")
|
| 209 |
+
|
| 210 |
+
for i, q in enumerate(self.questions):
|
| 211 |
+
if not q.get('question'):
|
| 212 |
+
issues.append(f"Question {i} is missing question text")
|
| 213 |
+
if not q.get('answer'):
|
| 214 |
+
issues.append(f"Question {i} is missing answer")
|
| 215 |
+
|
| 216 |
+
return issues
|
| 217 |
+
|
| 218 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 219 |
+
"""Convert benchmark to dictionary."""
|
| 220 |
+
return {
|
| 221 |
+
'name': self.name,
|
| 222 |
+
'description': self.description,
|
| 223 |
+
'domain': self.domain,
|
| 224 |
+
'difficulty': self.difficulty,
|
| 225 |
+
'passing_score': self.passing_score,
|
| 226 |
+
'created_at': self.created_at,
|
| 227 |
+
'categories': self.categories,
|
| 228 |
+
'tags': self.tags,
|
| 229 |
+
'num_questions': len(self.questions),
|
| 230 |
+
'questions': self.questions,
|
| 231 |
+
'metadata': self.metadata,
|
| 232 |
+
'statistics': self.get_statistics()
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
@classmethod
|
| 236 |
+
def from_dict(cls, data: Dict[str, Any]) -> 'Benchmark':
|
| 237 |
+
"""Create benchmark from dictionary."""
|
| 238 |
+
return cls(
|
| 239 |
+
name=data.get('name', 'Untitled'),
|
| 240 |
+
description=data.get('description', ''),
|
| 241 |
+
questions=data.get('questions', []),
|
| 242 |
+
metadata=data.get('metadata', {}),
|
| 243 |
+
created_at=data.get('created_at'),
|
| 244 |
+
domain=data.get('domain', 'general'),
|
| 245 |
+
difficulty=data.get('difficulty', 'mixed'),
|
| 246 |
+
passing_score=data.get('passing_score', 70.0),
|
| 247 |
+
categories=data.get('categories', []),
|
| 248 |
+
tags=data.get('tags', [])
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
def save(self, filepath: str):
|
| 252 |
+
"""Save benchmark to JSON file."""
|
| 253 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 254 |
+
|
| 255 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 256 |
+
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
|
| 257 |
+
|
| 258 |
+
print(f"Benchmark saved to: {filepath}")
|
| 259 |
+
|
| 260 |
+
@classmethod
|
| 261 |
+
def load(cls, filepath: str) -> 'Benchmark':
|
| 262 |
+
"""Load benchmark from JSON file."""
|
| 263 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 264 |
+
data = json.load(f)
|
| 265 |
+
|
| 266 |
+
return cls.from_dict(data)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
class BenchmarkSuite:
|
| 270 |
+
"""
|
| 271 |
+
Collection of benchmarks with enhanced building features.
|
| 272 |
+
"""
|
| 273 |
+
|
| 274 |
+
def __init__(self, name: str = "Default Suite"):
|
| 275 |
+
"""
|
| 276 |
+
Initialize benchmark suite.
|
| 277 |
+
|
| 278 |
+
Args:
|
| 279 |
+
name: Suite name
|
| 280 |
+
"""
|
| 281 |
+
self.name = name
|
| 282 |
+
self.description = ""
|
| 283 |
+
self.benchmarks: List[Benchmark] = []
|
| 284 |
+
self.metadata: Dict[str, Any] = {}
|
| 285 |
+
|
| 286 |
+
def add_benchmark(self, benchmark: Benchmark):
|
| 287 |
+
"""Add a benchmark to the suite."""
|
| 288 |
+
self.benchmarks.append(benchmark)
|
| 289 |
+
print(f"Added benchmark: {benchmark.name}")
|
| 290 |
+
|
| 291 |
+
def create_benchmark(
|
| 292 |
+
self,
|
| 293 |
+
name: str,
|
| 294 |
+
description: str = "",
|
| 295 |
+
domain: str = "general",
|
| 296 |
+
passing_score: float = 70.0
|
| 297 |
+
) -> Benchmark:
|
| 298 |
+
"""
|
| 299 |
+
Create a new benchmark and add to suite.
|
| 300 |
+
|
| 301 |
+
Args:
|
| 302 |
+
name: Benchmark name
|
| 303 |
+
description: Benchmark description
|
| 304 |
+
domain: Domain/topic
|
| 305 |
+
passing_score: Passing score percentage
|
| 306 |
+
|
| 307 |
+
Returns:
|
| 308 |
+
Created benchmark
|
| 309 |
+
"""
|
| 310 |
+
benchmark = Benchmark(
|
| 311 |
+
name=name,
|
| 312 |
+
description=description,
|
| 313 |
+
domain=domain,
|
| 314 |
+
passing_score=passing_score
|
| 315 |
+
)
|
| 316 |
+
self.add_benchmark(benchmark)
|
| 317 |
+
return benchmark
|
| 318 |
+
|
| 319 |
+
def get_benchmark(self, name: str) -> Optional[Benchmark]:
|
| 320 |
+
"""Get a benchmark by name."""
|
| 321 |
+
for benchmark in self.benchmarks:
|
| 322 |
+
if benchmark.name == name:
|
| 323 |
+
return benchmark
|
| 324 |
+
return None
|
| 325 |
+
|
| 326 |
+
def remove_benchmark(self, benchmark_name: str):
|
| 327 |
+
"""Remove a benchmark by name."""
|
| 328 |
+
self.benchmarks = [b for b in self.benchmarks if b.name != benchmark_name]
|
| 329 |
+
|
| 330 |
+
def list_benchmarks(self) -> List[str]:
|
| 331 |
+
"""Get list of benchmark names."""
|
| 332 |
+
return [b.name for b in self.benchmarks]
|
| 333 |
+
|
| 334 |
+
def get_statistics(self) -> Dict[str, Any]:
|
| 335 |
+
"""Get suite-wide statistics."""
|
| 336 |
+
total_questions = sum(len(b.questions) for b in self.benchmarks)
|
| 337 |
+
|
| 338 |
+
stats = {
|
| 339 |
+
'num_benchmarks': len(self.benchmarks),
|
| 340 |
+
'total_questions': total_questions,
|
| 341 |
+
'benchmarks': []
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
for benchmark in self.benchmarks:
|
| 345 |
+
stats['benchmarks'].append({
|
| 346 |
+
'name': benchmark.name,
|
| 347 |
+
'num_questions': len(benchmark.questions),
|
| 348 |
+
'domain': benchmark.domain,
|
| 349 |
+
'passing_score': benchmark.passing_score
|
| 350 |
+
})
|
| 351 |
+
|
| 352 |
+
return stats
|
| 353 |
+
|
| 354 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 355 |
+
"""Convert suite to dictionary."""
|
| 356 |
+
return {
|
| 357 |
+
'name': self.name,
|
| 358 |
+
'description': self.description,
|
| 359 |
+
'num_benchmarks': len(self.benchmarks),
|
| 360 |
+
'benchmarks': [b.to_dict() for b in self.benchmarks],
|
| 361 |
+
'metadata': self.metadata,
|
| 362 |
+
'statistics': self.get_statistics()
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
@classmethod
|
| 366 |
+
def from_dict(cls, data: Dict[str, Any]) -> 'BenchmarkSuite':
|
| 367 |
+
"""Create suite from dictionary."""
|
| 368 |
+
suite = cls(name=data.get('name', 'Default Suite'))
|
| 369 |
+
suite.description = data.get('description', '')
|
| 370 |
+
suite.metadata = data.get('metadata', {})
|
| 371 |
+
|
| 372 |
+
for benchmark_data in data.get('benchmarks', []):
|
| 373 |
+
benchmark = Benchmark.from_dict(benchmark_data)
|
| 374 |
+
suite.add_benchmark(benchmark)
|
| 375 |
+
|
| 376 |
+
return suite
|
| 377 |
+
|
| 378 |
+
def save(self, filepath: str):
|
| 379 |
+
"""Save suite to JSON file."""
|
| 380 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 381 |
+
|
| 382 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 383 |
+
json.dump(self.to_dict(), f, indent=2, ensure_ascii=False)
|
| 384 |
+
|
| 385 |
+
print(f"Benchmark suite saved to: {filepath}")
|
| 386 |
+
|
| 387 |
+
@classmethod
|
| 388 |
+
def load(cls, filepath: str) -> 'BenchmarkSuite':
|
| 389 |
+
"""Load suite from JSON file."""
|
| 390 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 391 |
+
data = json.load(f)
|
| 392 |
+
|
| 393 |
+
return cls.from_dict(data)
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
class InteractiveBenchmarkBuilder:
|
| 397 |
+
"""
|
| 398 |
+
Interactive builder for creating benchmarks through UI/CLI.
|
| 399 |
+
"""
|
| 400 |
+
|
| 401 |
+
def __init__(self):
|
| 402 |
+
"""Initialize builder."""
|
| 403 |
+
self.current_benchmark: Optional[Benchmark] = None
|
| 404 |
+
self.current_suite: Optional[BenchmarkSuite] = None
|
| 405 |
+
|
| 406 |
+
def create_benchmark(
|
| 407 |
+
self,
|
| 408 |
+
name: str,
|
| 409 |
+
description: str = "",
|
| 410 |
+
domain: str = "general"
|
| 411 |
+
) -> Benchmark:
|
| 412 |
+
"""
|
| 413 |
+
Create a new benchmark.
|
| 414 |
+
|
| 415 |
+
Args:
|
| 416 |
+
name: Benchmark name
|
| 417 |
+
description: Description
|
| 418 |
+
domain: Domain/topic
|
| 419 |
+
|
| 420 |
+
Returns:
|
| 421 |
+
Created benchmark
|
| 422 |
+
"""
|
| 423 |
+
self.current_benchmark = Benchmark(
|
| 424 |
+
name=name,
|
| 425 |
+
description=description,
|
| 426 |
+
domain=domain
|
| 427 |
+
)
|
| 428 |
+
return self.current_benchmark
|
| 429 |
+
|
| 430 |
+
def add_question_interactive(
|
| 431 |
+
self,
|
| 432 |
+
question: str,
|
| 433 |
+
answer: str,
|
| 434 |
+
category: Optional[str] = None,
|
| 435 |
+
difficulty: Optional[str] = None
|
| 436 |
+
) -> bool:
|
| 437 |
+
"""
|
| 438 |
+
Add question to current benchmark.
|
| 439 |
+
|
| 440 |
+
Args:
|
| 441 |
+
question: Question text
|
| 442 |
+
answer: Answer text
|
| 443 |
+
category: Category
|
| 444 |
+
difficulty: Difficulty level
|
| 445 |
+
|
| 446 |
+
Returns:
|
| 447 |
+
Success status
|
| 448 |
+
"""
|
| 449 |
+
if not self.current_benchmark:
|
| 450 |
+
print("No active benchmark. Create one first.")
|
| 451 |
+
return False
|
| 452 |
+
|
| 453 |
+
self.current_benchmark.add_question(
|
| 454 |
+
question=question,
|
| 455 |
+
answer=answer,
|
| 456 |
+
category=category,
|
| 457 |
+
difficulty=difficulty
|
| 458 |
+
)
|
| 459 |
+
return True
|
| 460 |
+
|
| 461 |
+
def preview_benchmark(self) -> str:
|
| 462 |
+
"""Preview current benchmark."""
|
| 463 |
+
if not self.current_benchmark:
|
| 464 |
+
return "No active benchmark"
|
| 465 |
+
|
| 466 |
+
stats = self.current_benchmark.get_statistics()
|
| 467 |
+
|
| 468 |
+
preview = f"""
|
| 469 |
+
Benchmark: {self.current_benchmark.name}
|
| 470 |
+
Description: {self.current_benchmark.description}
|
| 471 |
+
Domain: {self.current_benchmark.domain}
|
| 472 |
+
Total Questions: {stats['total_questions']}
|
| 473 |
+
|
| 474 |
+
Categories:
|
| 475 |
+
"""
|
| 476 |
+
for cat, count in stats['categories'].items():
|
| 477 |
+
preview += f" - {cat}: {count} questions\n"
|
| 478 |
+
|
| 479 |
+
return preview
|
| 480 |
+
|
| 481 |
+
def finalize_benchmark(self, filepath: Optional[str] = None) -> Benchmark:
|
| 482 |
+
"""
|
| 483 |
+
Finalize and optionally save benchmark.
|
| 484 |
+
|
| 485 |
+
Args:
|
| 486 |
+
filepath: Optional save path
|
| 487 |
+
|
| 488 |
+
Returns:
|
| 489 |
+
Finalized benchmark
|
| 490 |
+
"""
|
| 491 |
+
if not self.current_benchmark:
|
| 492 |
+
raise ValueError("No active benchmark to finalize")
|
| 493 |
+
|
| 494 |
+
issues = self.current_benchmark.validate()
|
| 495 |
+
if issues:
|
| 496 |
+
print("Validation warnings:")
|
| 497 |
+
for issue in issues:
|
| 498 |
+
print(f" - {issue}")
|
| 499 |
+
|
| 500 |
+
if filepath:
|
| 501 |
+
self.current_benchmark.save(filepath)
|
| 502 |
+
|
| 503 |
+
benchmark = self.current_benchmark
|
| 504 |
+
self.current_benchmark = None
|
| 505 |
+
return benchmark
|
evaluation/certification_tests.py
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Certification Tests Module
|
| 3 |
+
|
| 4 |
+
Provides pre-built certification exam benchmarks for various domains.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from typing import List, Dict, Optional, Any
|
| 8 |
+
import json
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from dataclasses import dataclass, field
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass
|
| 14 |
+
class CertificationQuestion:
|
| 15 |
+
"""A single certification exam question."""
|
| 16 |
+
question: str
|
| 17 |
+
answer: str
|
| 18 |
+
category: str
|
| 19 |
+
difficulty: str = "intermediate"
|
| 20 |
+
points: float = 1.0
|
| 21 |
+
explanation: Optional[str] = None
|
| 22 |
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class CertificationTestBuilder:
|
| 26 |
+
"""
|
| 27 |
+
Base class for building certification tests.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, name: str, domain: str):
|
| 31 |
+
"""
|
| 32 |
+
Initialize certification test.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
name: Test name
|
| 36 |
+
domain: Domain (e.g., 'financial', 'medical')
|
| 37 |
+
"""
|
| 38 |
+
self.name = name
|
| 39 |
+
self.domain = domain
|
| 40 |
+
self.questions: List[CertificationQuestion] = []
|
| 41 |
+
self.passing_score = 70.0
|
| 42 |
+
self.categories: List[str] = []
|
| 43 |
+
|
| 44 |
+
def add_question(
|
| 45 |
+
self,
|
| 46 |
+
question: str,
|
| 47 |
+
answer: str,
|
| 48 |
+
category: str,
|
| 49 |
+
difficulty: str = "intermediate",
|
| 50 |
+
explanation: Optional[str] = None
|
| 51 |
+
):
|
| 52 |
+
"""Add a question to the test."""
|
| 53 |
+
q = CertificationQuestion(
|
| 54 |
+
question=question,
|
| 55 |
+
answer=answer,
|
| 56 |
+
category=category,
|
| 57 |
+
difficulty=difficulty,
|
| 58 |
+
explanation=explanation
|
| 59 |
+
)
|
| 60 |
+
self.questions.append(q)
|
| 61 |
+
|
| 62 |
+
if category not in self.categories:
|
| 63 |
+
self.categories.append(category)
|
| 64 |
+
|
| 65 |
+
def get_questions_by_category(self, category: str) -> List[CertificationQuestion]:
|
| 66 |
+
"""Get all questions in a category."""
|
| 67 |
+
return [q for q in self.questions if q.category == category]
|
| 68 |
+
|
| 69 |
+
def to_benchmark_dict(self) -> Dict[str, Any]:
|
| 70 |
+
"""Convert to benchmark format."""
|
| 71 |
+
return {
|
| 72 |
+
'name': self.name,
|
| 73 |
+
'domain': self.domain,
|
| 74 |
+
'description': f"{self.name} Certification Exam",
|
| 75 |
+
'passing_score': self.passing_score,
|
| 76 |
+
'num_questions': len(self.questions),
|
| 77 |
+
'categories': self.categories,
|
| 78 |
+
'questions': [
|
| 79 |
+
{
|
| 80 |
+
'question': q.question,
|
| 81 |
+
'answer': q.answer,
|
| 82 |
+
'category': q.category,
|
| 83 |
+
'difficulty': q.difficulty,
|
| 84 |
+
'points': q.points,
|
| 85 |
+
'explanation': q.explanation,
|
| 86 |
+
'metadata': q.metadata
|
| 87 |
+
}
|
| 88 |
+
for q in self.questions
|
| 89 |
+
]
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
def save(self, filepath: str):
|
| 93 |
+
"""Save test to JSON."""
|
| 94 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 95 |
+
|
| 96 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 97 |
+
json.dump(self.to_benchmark_dict(), f, indent=2, ensure_ascii=False)
|
| 98 |
+
|
| 99 |
+
print(f"Certification test saved to: {filepath}")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class FinancialCertificationTests:
|
| 103 |
+
"""
|
| 104 |
+
Pre-built financial certification tests.
|
| 105 |
+
|
| 106 |
+
Includes:
|
| 107 |
+
- CFP (Certified Financial Planner)
|
| 108 |
+
- CFA (Chartered Financial Analyst)
|
| 109 |
+
- CPA (Certified Public Accountant)
|
| 110 |
+
- Series 7, Series 63
|
| 111 |
+
"""
|
| 112 |
+
|
| 113 |
+
@staticmethod
|
| 114 |
+
def create_cfp_exam() -> CertificationTestBuilder:
|
| 115 |
+
"""Create CFP certification exam."""
|
| 116 |
+
exam = CertificationTestBuilder("CFP (Certified Financial Planner)", "financial")
|
| 117 |
+
exam.passing_score = 70.0
|
| 118 |
+
|
| 119 |
+
# Estate Planning
|
| 120 |
+
exam.add_question(
|
| 121 |
+
"What is the primary purpose of a revocable living trust?",
|
| 122 |
+
"A revocable living trust allows assets to pass to beneficiaries without going through probate while maintaining the grantor's control during their lifetime.",
|
| 123 |
+
category="Estate Planning",
|
| 124 |
+
difficulty="intermediate"
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
exam.add_question(
|
| 128 |
+
"What is the annual gift tax exclusion for 2024?",
|
| 129 |
+
"The annual gift tax exclusion for 2024 is $18,000 per recipient.",
|
| 130 |
+
category="Estate Planning",
|
| 131 |
+
difficulty="beginner"
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
# Retirement Planning
|
| 135 |
+
exam.add_question(
|
| 136 |
+
"What are the contribution limits for a 401(k) plan in 2024 for individuals under 50?",
|
| 137 |
+
"The contribution limit for 401(k) plans in 2024 is $23,000 for individuals under age 50.",
|
| 138 |
+
category="Retirement Planning",
|
| 139 |
+
difficulty="beginner"
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
exam.add_question(
|
| 143 |
+
"Explain the difference between a traditional IRA and a Roth IRA in terms of tax treatment.",
|
| 144 |
+
"Traditional IRA contributions may be tax-deductible and earnings grow tax-deferred, with distributions taxed as ordinary income. Roth IRA contributions are made with after-tax dollars, but qualified distributions are tax-free.",
|
| 145 |
+
category="Retirement Planning",
|
| 146 |
+
difficulty="intermediate"
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
# Tax Planning
|
| 150 |
+
exam.add_question(
|
| 151 |
+
"What is the long-term capital gains tax rate for high-income earners in 2024?",
|
| 152 |
+
"The long-term capital gains tax rate for high-income earners is 20%, plus a potential 3.8% Net Investment Income Tax.",
|
| 153 |
+
category="Tax Planning",
|
| 154 |
+
difficulty="intermediate"
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
# Investment Planning
|
| 158 |
+
exam.add_question(
|
| 159 |
+
"What is Modern Portfolio Theory and who developed it?",
|
| 160 |
+
"Modern Portfolio Theory, developed by Harry Markowitz, suggests that investors can construct an optimal portfolio by considering the relationship between risk and return, focusing on diversification to maximize returns for a given level of risk.",
|
| 161 |
+
category="Investment Planning",
|
| 162 |
+
difficulty="advanced"
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
exam.add_question(
|
| 166 |
+
"Explain the concept of asset allocation.",
|
| 167 |
+
"Asset allocation is the process of dividing investments among different asset classes (stocks, bonds, cash, etc.) to balance risk and reward according to an investor's goals, risk tolerance, and time horizon.",
|
| 168 |
+
category="Investment Planning",
|
| 169 |
+
difficulty="intermediate"
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
# Insurance Planning
|
| 173 |
+
exam.add_question(
|
| 174 |
+
"What is the primary difference between term life insurance and whole life insurance?",
|
| 175 |
+
"Term life insurance provides coverage for a specific period and has no cash value, while whole life insurance provides lifetime coverage and builds cash value over time.",
|
| 176 |
+
category="Insurance Planning",
|
| 177 |
+
difficulty="beginner"
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
# Risk Management
|
| 181 |
+
exam.add_question(
|
| 182 |
+
"What is an umbrella insurance policy?",
|
| 183 |
+
"An umbrella insurance policy provides additional liability coverage beyond the limits of homeowners, auto, and other personal insurance policies.",
|
| 184 |
+
category="Risk Management",
|
| 185 |
+
difficulty="intermediate"
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
# Education Planning
|
| 189 |
+
exam.add_question(
|
| 190 |
+
"What are the tax advantages of a 529 college savings plan?",
|
| 191 |
+
"529 plans offer tax-free growth and tax-free withdrawals when funds are used for qualified education expenses. Some states also provide state tax deductions for contributions.",
|
| 192 |
+
category="Education Planning",
|
| 193 |
+
difficulty="intermediate"
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
return exam
|
| 197 |
+
|
| 198 |
+
@staticmethod
|
| 199 |
+
def create_cfa_level_1_exam() -> CertificationTestBuilder:
|
| 200 |
+
"""Create CFA Level I exam."""
|
| 201 |
+
exam = CertificationTestBuilder("CFA Level I", "financial")
|
| 202 |
+
exam.passing_score = 70.0
|
| 203 |
+
|
| 204 |
+
# Ethics
|
| 205 |
+
exam.add_question(
|
| 206 |
+
"What are the six components of the CFA Institute Code of Ethics?",
|
| 207 |
+
"The six components are: Act with integrity, Place client interests first, Use reasonable care and judgment, Act professionally, Promote integrity of capital markets, and Maintain competence.",
|
| 208 |
+
category="Ethics",
|
| 209 |
+
difficulty="intermediate"
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
# Quantitative Methods
|
| 213 |
+
exam.add_question(
|
| 214 |
+
"What is the formula for calculating the present value of a perpetuity?",
|
| 215 |
+
"The present value of a perpetuity is calculated as: PV = PMT / r, where PMT is the payment and r is the discount rate.",
|
| 216 |
+
category="Quantitative Methods",
|
| 217 |
+
difficulty="intermediate"
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
# Economics
|
| 221 |
+
exam.add_question(
|
| 222 |
+
"Define GDP and explain the expenditure approach to measuring it.",
|
| 223 |
+
"GDP (Gross Domestic Product) is the total value of goods and services produced in an economy. The expenditure approach calculates GDP as: GDP = C + I + G + (X - M), where C is consumption, I is investment, G is government spending, X is exports, and M is imports.",
|
| 224 |
+
category="Economics",
|
| 225 |
+
difficulty="intermediate"
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
# Financial Reporting
|
| 229 |
+
exam.add_question(
|
| 230 |
+
"What are the three main financial statements?",
|
| 231 |
+
"The three main financial statements are the Balance Sheet (Statement of Financial Position), Income Statement (Statement of Comprehensive Income), and Cash Flow Statement.",
|
| 232 |
+
category="Financial Reporting",
|
| 233 |
+
difficulty="beginner"
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
# Corporate Finance
|
| 237 |
+
exam.add_question(
|
| 238 |
+
"What is the Weighted Average Cost of Capital (WACC)?",
|
| 239 |
+
"WACC is the average rate a company expects to pay to finance its assets, weighted by the proportion of debt and equity in its capital structure.",
|
| 240 |
+
category="Corporate Finance",
|
| 241 |
+
difficulty="intermediate"
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
# Equity Investments
|
| 245 |
+
exam.add_question(
|
| 246 |
+
"Explain the difference between value stocks and growth stocks.",
|
| 247 |
+
"Value stocks trade at lower prices relative to fundamentals (P/E, P/B) and typically pay dividends. Growth stocks are expected to grow earnings faster than the market average and typically reinvest earnings rather than paying dividends.",
|
| 248 |
+
category="Equity Investments",
|
| 249 |
+
difficulty="intermediate"
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
# Fixed Income
|
| 253 |
+
exam.add_question(
|
| 254 |
+
"What is duration and how does it relate to interest rate risk?",
|
| 255 |
+
"Duration measures a bond's price sensitivity to interest rate changes. Higher duration means greater price sensitivity. When interest rates rise, bond prices fall more for bonds with higher duration.",
|
| 256 |
+
category="Fixed Income",
|
| 257 |
+
difficulty="advanced"
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
# Derivatives
|
| 261 |
+
exam.add_question(
|
| 262 |
+
"What is a call option?",
|
| 263 |
+
"A call option gives the holder the right, but not the obligation, to buy an underlying asset at a specified strike price on or before a specified expiration date.",
|
| 264 |
+
category="Derivatives",
|
| 265 |
+
difficulty="beginner"
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
return exam
|
| 269 |
+
|
| 270 |
+
@staticmethod
|
| 271 |
+
def create_series_7_exam() -> CertificationTestBuilder:
|
| 272 |
+
"""Create Series 7 General Securities Representative exam."""
|
| 273 |
+
exam = CertificationTestBuilder("Series 7", "financial")
|
| 274 |
+
exam.passing_score = 72.0
|
| 275 |
+
|
| 276 |
+
# Regulations
|
| 277 |
+
exam.add_question(
|
| 278 |
+
"What is Regulation T and what does it govern?",
|
| 279 |
+
"Regulation T is a Federal Reserve Board regulation that governs customer cash accounts and the amount of credit that broker-dealers may extend to customers for securities purchases (currently 50% initial margin).",
|
| 280 |
+
category="Regulations",
|
| 281 |
+
difficulty="intermediate"
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
# Securities Products
|
| 285 |
+
exam.add_question(
|
| 286 |
+
"What is the difference between common stock and preferred stock?",
|
| 287 |
+
"Common stock provides voting rights and variable dividends based on company performance. Preferred stock typically has fixed dividends, no voting rights, and priority over common stock in liquidation.",
|
| 288 |
+
category="Securities Products",
|
| 289 |
+
difficulty="beginner"
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
# Municipal Securities
|
| 293 |
+
exam.add_question(
|
| 294 |
+
"What are the two main types of municipal bonds?",
|
| 295 |
+
"The two main types are General Obligation (GO) bonds, backed by the taxing power of the issuer, and Revenue bonds, backed by specific revenue sources like tolls or utility fees.",
|
| 296 |
+
category="Municipal Securities",
|
| 297 |
+
difficulty="intermediate"
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
# Options
|
| 301 |
+
exam.add_question(
|
| 302 |
+
"What is a covered call strategy?",
|
| 303 |
+
"A covered call strategy involves owning a stock and selling call options on that stock to generate income from the option premium while capping potential upside gains.",
|
| 304 |
+
category="Options",
|
| 305 |
+
difficulty="intermediate"
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
# Suitability
|
| 309 |
+
exam.add_question(
|
| 310 |
+
"What factors must be considered when determining investment suitability for a client?",
|
| 311 |
+
"Factors include the client's financial situation, tax status, investment objectives, time horizon, liquidity needs, risk tolerance, and investment experience.",
|
| 312 |
+
category="Suitability",
|
| 313 |
+
difficulty="intermediate"
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
return exam
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
class MedicalCertificationTests:
|
| 320 |
+
"""
|
| 321 |
+
Pre-built medical certification tests.
|
| 322 |
+
"""
|
| 323 |
+
|
| 324 |
+
@staticmethod
|
| 325 |
+
def create_usmle_step_1_sample() -> CertificationTestBuilder:
|
| 326 |
+
"""Create USMLE Step 1 sample questions."""
|
| 327 |
+
exam = CertificationTestBuilder("USMLE Step 1 Sample", "medical")
|
| 328 |
+
exam.passing_score = 70.0
|
| 329 |
+
|
| 330 |
+
# Anatomy
|
| 331 |
+
exam.add_question(
|
| 332 |
+
"Which cranial nerve is responsible for facial sensation and motor control of mastication?",
|
| 333 |
+
"The trigeminal nerve (cranial nerve V) is responsible for facial sensation and motor control of the muscles of mastication.",
|
| 334 |
+
category="Anatomy",
|
| 335 |
+
difficulty="intermediate"
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
# Physiology
|
| 339 |
+
exam.add_question(
|
| 340 |
+
"Describe the Frank-Starling mechanism of the heart.",
|
| 341 |
+
"The Frank-Starling mechanism states that the force of cardiac contraction is directly proportional to the initial length of cardiac muscle fibers (preload). Increased venous return stretches the ventricle, leading to stronger contraction.",
|
| 342 |
+
category="Physiology",
|
| 343 |
+
difficulty="advanced"
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
# Pharmacology
|
| 347 |
+
exam.add_question(
|
| 348 |
+
"What is the mechanism of action of ACE inhibitors?",
|
| 349 |
+
"ACE inhibitors block the angiotensin-converting enzyme, preventing the conversion of angiotensin I to angiotensin II, thereby reducing vasoconstriction and aldosterone secretion, which lowers blood pressure.",
|
| 350 |
+
category="Pharmacology",
|
| 351 |
+
difficulty="intermediate"
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
# Pathology
|
| 355 |
+
exam.add_question(
|
| 356 |
+
"What are the four cardinal signs of inflammation?",
|
| 357 |
+
"The four cardinal signs of inflammation are: rubor (redness), calor (heat), tumor (swelling), and dolor (pain). A fifth sign, loss of function, is sometimes added.",
|
| 358 |
+
category="Pathology",
|
| 359 |
+
difficulty="beginner"
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
return exam
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
class LegalCertificationTests:
|
| 366 |
+
"""
|
| 367 |
+
Pre-built legal certification tests.
|
| 368 |
+
"""
|
| 369 |
+
|
| 370 |
+
@staticmethod
|
| 371 |
+
def create_bar_exam_sample() -> CertificationTestBuilder:
|
| 372 |
+
"""Create Bar Exam sample questions."""
|
| 373 |
+
exam = CertificationTestBuilder("Bar Exam Sample", "legal")
|
| 374 |
+
exam.passing_score = 70.0
|
| 375 |
+
|
| 376 |
+
# Constitutional Law
|
| 377 |
+
exam.add_question(
|
| 378 |
+
"What is the difference between strict scrutiny and rational basis review?",
|
| 379 |
+
"Strict scrutiny requires the government to prove a compelling interest and narrowly tailored means (used for fundamental rights). Rational basis review only requires a legitimate interest and rational relationship (default standard).",
|
| 380 |
+
category="Constitutional Law",
|
| 381 |
+
difficulty="advanced"
|
| 382 |
+
)
|
| 383 |
+
|
| 384 |
+
# Contracts
|
| 385 |
+
exam.add_question(
|
| 386 |
+
"What are the essential elements of a valid contract?",
|
| 387 |
+
"The essential elements are: offer, acceptance, consideration, mutual assent (meeting of minds), capacity, and legality of purpose.",
|
| 388 |
+
category="Contracts",
|
| 389 |
+
difficulty="intermediate"
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
# Torts
|
| 393 |
+
exam.add_question(
|
| 394 |
+
"What are the elements of negligence?",
|
| 395 |
+
"The elements of negligence are: duty of care, breach of duty, causation (actual and proximate), and damages.",
|
| 396 |
+
category="Torts",
|
| 397 |
+
difficulty="intermediate"
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
# Criminal Law
|
| 401 |
+
exam.add_question(
|
| 402 |
+
"What is mens rea?",
|
| 403 |
+
"Mens rea is the mental state or intent element of a crime. It refers to the defendant's guilty mind or criminal intent at the time the criminal act was committed.",
|
| 404 |
+
category="Criminal Law",
|
| 405 |
+
difficulty="beginner"
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
return exam
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
class EducationCertificationTests:
|
| 412 |
+
"""
|
| 413 |
+
Pre-built education certification tests.
|
| 414 |
+
"""
|
| 415 |
+
|
| 416 |
+
@staticmethod
|
| 417 |
+
def create_praxis_core_sample() -> CertificationTestBuilder:
|
| 418 |
+
"""Create Praxis Core sample test."""
|
| 419 |
+
exam = CertificationTestBuilder("Praxis Core Sample", "education")
|
| 420 |
+
exam.passing_score = 70.0
|
| 421 |
+
|
| 422 |
+
# Reading
|
| 423 |
+
exam.add_question(
|
| 424 |
+
"What is the main idea vs. supporting details in a passage?",
|
| 425 |
+
"The main idea is the central point or primary message of a passage, while supporting details provide evidence, examples, or explanations that reinforce the main idea.",
|
| 426 |
+
category="Reading",
|
| 427 |
+
difficulty="beginner"
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
# Writing
|
| 431 |
+
exam.add_question(
|
| 432 |
+
"What is the difference between an independent clause and a dependent clause?",
|
| 433 |
+
"An independent clause can stand alone as a complete sentence with a subject and predicate. A dependent clause cannot stand alone and depends on an independent clause to form a complete thought.",
|
| 434 |
+
category="Writing",
|
| 435 |
+
difficulty="intermediate"
|
| 436 |
+
)
|
| 437 |
+
|
| 438 |
+
# Mathematics
|
| 439 |
+
exam.add_question(
|
| 440 |
+
"What is the order of operations in mathematics?",
|
| 441 |
+
"The order of operations is: Parentheses, Exponents, Multiplication and Division (left to right), Addition and Subtraction (left to right), often remembered by PEMDAS.",
|
| 442 |
+
category="Mathematics",
|
| 443 |
+
difficulty="beginner"
|
| 444 |
+
)
|
| 445 |
+
|
| 446 |
+
return exam
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
def get_certification_tests_for_domain(domain: str) -> List[CertificationTestBuilder]:
|
| 450 |
+
"""
|
| 451 |
+
Get all certification tests for a specific domain.
|
| 452 |
+
|
| 453 |
+
Args:
|
| 454 |
+
domain: Domain name (financial, medical, legal, education)
|
| 455 |
+
|
| 456 |
+
Returns:
|
| 457 |
+
List of certification tests
|
| 458 |
+
"""
|
| 459 |
+
tests = []
|
| 460 |
+
|
| 461 |
+
if domain.lower() == "financial":
|
| 462 |
+
tests.append(FinancialCertificationTests.create_cfp_exam())
|
| 463 |
+
tests.append(FinancialCertificationTests.create_cfa_level_1_exam())
|
| 464 |
+
tests.append(FinancialCertificationTests.create_series_7_exam())
|
| 465 |
+
|
| 466 |
+
elif domain.lower() == "medical":
|
| 467 |
+
tests.append(MedicalCertificationTests.create_usmle_step_1_sample())
|
| 468 |
+
|
| 469 |
+
elif domain.lower() == "legal":
|
| 470 |
+
tests.append(LegalCertificationTests.create_bar_exam_sample())
|
| 471 |
+
|
| 472 |
+
elif domain.lower() == "education":
|
| 473 |
+
tests.append(EducationCertificationTests.create_praxis_core_sample())
|
| 474 |
+
|
| 475 |
+
return tests
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def list_available_certification_tests() -> Dict[str, List[str]]:
|
| 479 |
+
"""
|
| 480 |
+
List all available certification tests by domain.
|
| 481 |
+
|
| 482 |
+
Returns:
|
| 483 |
+
Dict mapping domain to test names
|
| 484 |
+
"""
|
| 485 |
+
return {
|
| 486 |
+
'financial': [
|
| 487 |
+
'CFP (Certified Financial Planner)',
|
| 488 |
+
'CFA Level I',
|
| 489 |
+
'Series 7'
|
| 490 |
+
],
|
| 491 |
+
'medical': [
|
| 492 |
+
'USMLE Step 1 Sample'
|
| 493 |
+
],
|
| 494 |
+
'legal': [
|
| 495 |
+
'Bar Exam Sample'
|
| 496 |
+
],
|
| 497 |
+
'education': [
|
| 498 |
+
'Praxis Core Sample'
|
| 499 |
+
]
|
| 500 |
+
}
|
evaluation/evaluator.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Model Evaluator Module
|
| 3 |
+
|
| 4 |
+
Provides model evaluation and inference capabilities.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass, field
|
| 8 |
+
from typing import List, Dict, Optional, Any
|
| 9 |
+
import json
|
| 10 |
+
import time
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass
|
| 16 |
+
class EvaluationConfig:
|
| 17 |
+
"""
|
| 18 |
+
Configuration for model evaluation.
|
| 19 |
+
"""
|
| 20 |
+
model_name: str = "gpt2"
|
| 21 |
+
model_path: Optional[str] = None
|
| 22 |
+
max_length: int = 512
|
| 23 |
+
temperature: float = 0.7
|
| 24 |
+
top_p: float = 0.9
|
| 25 |
+
top_k: int = 50
|
| 26 |
+
num_beams: int = 1
|
| 27 |
+
do_sample: bool = True
|
| 28 |
+
batch_size: int = 1
|
| 29 |
+
device: str = "auto"
|
| 30 |
+
max_samples: Optional[int] = None
|
| 31 |
+
save_results: bool = True
|
| 32 |
+
output_dir: str = "evaluation_results"
|
| 33 |
+
include_metrics: bool = True
|
| 34 |
+
include_timings: bool = True
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class ModelEvaluator:
|
| 38 |
+
"""
|
| 39 |
+
Model evaluator for testing and benchmarking LLMs.
|
| 40 |
+
|
| 41 |
+
Features:
|
| 42 |
+
- Inference on test datasets
|
| 43 |
+
- Batch processing
|
| 44 |
+
- Performance metrics
|
| 45 |
+
- Result saving and analysis
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
config: Optional[EvaluationConfig] = None,
|
| 51 |
+
model: Optional[Any] = None,
|
| 52 |
+
tokenizer: Optional[Any] = None
|
| 53 |
+
):
|
| 54 |
+
"""
|
| 55 |
+
Initialize evaluator.
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
config: Evaluation configuration
|
| 59 |
+
model: Pre-loaded model (optional)
|
| 60 |
+
tokenizer: Pre-loaded tokenizer (optional)
|
| 61 |
+
"""
|
| 62 |
+
self.config = config or EvaluationConfig()
|
| 63 |
+
self.model = model
|
| 64 |
+
self.tokenizer = tokenizer
|
| 65 |
+
self.results = []
|
| 66 |
+
self.metrics = {}
|
| 67 |
+
|
| 68 |
+
def load_model(self):
|
| 69 |
+
"""Load model and tokenizer."""
|
| 70 |
+
if self.model is not None and self.tokenizer is not None:
|
| 71 |
+
print("Using pre-loaded model and tokenizer")
|
| 72 |
+
return
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 76 |
+
|
| 77 |
+
print(f"Loading model: {self.config.model_name}")
|
| 78 |
+
|
| 79 |
+
# Load tokenizer
|
| 80 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 81 |
+
self.config.model_path or self.config.model_name,
|
| 82 |
+
trust_remote_code=True
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
# Ensure pad token exists
|
| 86 |
+
if self.tokenizer.pad_token is None:
|
| 87 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 88 |
+
|
| 89 |
+
# Load model
|
| 90 |
+
device_map = "auto" if self.config.device == "auto" else None
|
| 91 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 92 |
+
self.config.model_path or self.config.model_name,
|
| 93 |
+
device_map=device_map,
|
| 94 |
+
trust_remote_code=True,
|
| 95 |
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
self.model.eval()
|
| 99 |
+
print("Model loaded successfully")
|
| 100 |
+
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"Error loading model: {e}")
|
| 103 |
+
raise
|
| 104 |
+
|
| 105 |
+
def generate_response(
|
| 106 |
+
self,
|
| 107 |
+
prompt: str,
|
| 108 |
+
max_length: Optional[int] = None,
|
| 109 |
+
temperature: Optional[float] = None
|
| 110 |
+
) -> str:
|
| 111 |
+
"""
|
| 112 |
+
Generate response for a single prompt.
|
| 113 |
+
|
| 114 |
+
Args:
|
| 115 |
+
prompt: Input prompt
|
| 116 |
+
max_length: Max generation length
|
| 117 |
+
temperature: Sampling temperature
|
| 118 |
+
|
| 119 |
+
Returns:
|
| 120 |
+
Generated text
|
| 121 |
+
"""
|
| 122 |
+
if self.model is None or self.tokenizer is None:
|
| 123 |
+
self.load_model()
|
| 124 |
+
|
| 125 |
+
max_length = max_length or self.config.max_length
|
| 126 |
+
temperature = temperature or self.config.temperature
|
| 127 |
+
|
| 128 |
+
try:
|
| 129 |
+
# Tokenize
|
| 130 |
+
inputs = self.tokenizer(
|
| 131 |
+
prompt,
|
| 132 |
+
return_tensors="pt",
|
| 133 |
+
truncation=True,
|
| 134 |
+
max_length=2048
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
# Move to device
|
| 138 |
+
if hasattr(self.model, 'device'):
|
| 139 |
+
inputs = {k: v.to(self.model.device) for k, v in inputs.items()}
|
| 140 |
+
|
| 141 |
+
# Generate
|
| 142 |
+
with torch.no_grad():
|
| 143 |
+
outputs = self.model.generate(
|
| 144 |
+
**inputs,
|
| 145 |
+
max_length=max_length,
|
| 146 |
+
temperature=temperature,
|
| 147 |
+
top_p=self.config.top_p,
|
| 148 |
+
top_k=self.config.top_k,
|
| 149 |
+
num_beams=self.config.num_beams,
|
| 150 |
+
do_sample=self.config.do_sample,
|
| 151 |
+
pad_token_id=self.tokenizer.pad_token_id,
|
| 152 |
+
eos_token_id=self.tokenizer.eos_token_id
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
# Decode
|
| 156 |
+
generated_text = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 157 |
+
|
| 158 |
+
# Remove prompt from output
|
| 159 |
+
if generated_text.startswith(prompt):
|
| 160 |
+
generated_text = generated_text[len(prompt):].strip()
|
| 161 |
+
|
| 162 |
+
return generated_text
|
| 163 |
+
|
| 164 |
+
except Exception as e:
|
| 165 |
+
print(f"Generation error: {e}")
|
| 166 |
+
return f"[Error: {str(e)}]"
|
| 167 |
+
|
| 168 |
+
def evaluate_dataset(
|
| 169 |
+
self,
|
| 170 |
+
dataset: List[Dict[str, str]],
|
| 171 |
+
max_samples: Optional[int] = None
|
| 172 |
+
) -> Dict[str, Any]:
|
| 173 |
+
"""
|
| 174 |
+
Evaluate model on a dataset.
|
| 175 |
+
|
| 176 |
+
Args:
|
| 177 |
+
dataset: List of examples with 'instruction', 'input', 'output'
|
| 178 |
+
max_samples: Maximum samples to evaluate
|
| 179 |
+
|
| 180 |
+
Returns:
|
| 181 |
+
Evaluation results
|
| 182 |
+
"""
|
| 183 |
+
if self.model is None:
|
| 184 |
+
self.load_model()
|
| 185 |
+
|
| 186 |
+
max_samples = max_samples or self.config.max_samples or len(dataset)
|
| 187 |
+
dataset = dataset[:max_samples]
|
| 188 |
+
|
| 189 |
+
print(f"\nEvaluating on {len(dataset)} examples...")
|
| 190 |
+
|
| 191 |
+
results = []
|
| 192 |
+
predictions = []
|
| 193 |
+
references = []
|
| 194 |
+
start_time = time.time()
|
| 195 |
+
|
| 196 |
+
for i, example in enumerate(dataset):
|
| 197 |
+
# Build prompt
|
| 198 |
+
instruction = example.get('instruction', '')
|
| 199 |
+
input_text = example.get('input', '')
|
| 200 |
+
reference = example.get('output', '')
|
| 201 |
+
|
| 202 |
+
if input_text:
|
| 203 |
+
prompt = f"{instruction}\n\nInput: {input_text}\n\nResponse:"
|
| 204 |
+
else:
|
| 205 |
+
prompt = f"{instruction}\n\nResponse:"
|
| 206 |
+
|
| 207 |
+
# Generate
|
| 208 |
+
example_start = time.time()
|
| 209 |
+
prediction = self.generate_response(prompt)
|
| 210 |
+
example_time = time.time() - example_start
|
| 211 |
+
|
| 212 |
+
# Store results
|
| 213 |
+
result = {
|
| 214 |
+
'index': i,
|
| 215 |
+
'instruction': instruction,
|
| 216 |
+
'input': input_text,
|
| 217 |
+
'reference': reference,
|
| 218 |
+
'prediction': prediction,
|
| 219 |
+
'generation_time': example_time
|
| 220 |
+
}
|
| 221 |
+
results.append(result)
|
| 222 |
+
predictions.append(prediction)
|
| 223 |
+
references.append(reference)
|
| 224 |
+
|
| 225 |
+
if (i + 1) % 10 == 0:
|
| 226 |
+
print(f" Processed {i + 1}/{len(dataset)} examples...")
|
| 227 |
+
|
| 228 |
+
total_time = time.time() - start_time
|
| 229 |
+
|
| 230 |
+
# Calculate metrics
|
| 231 |
+
metrics = {}
|
| 232 |
+
if self.config.include_metrics:
|
| 233 |
+
try:
|
| 234 |
+
from .metrics import Metrics
|
| 235 |
+
metrics_calc = Metrics()
|
| 236 |
+
metrics = metrics_calc.calculate_all_metrics(predictions, references)
|
| 237 |
+
except Exception as e:
|
| 238 |
+
print(f"Metrics calculation error: {e}")
|
| 239 |
+
metrics = {'error': str(e)}
|
| 240 |
+
|
| 241 |
+
# Compile results
|
| 242 |
+
evaluation_results = {
|
| 243 |
+
'config': {
|
| 244 |
+
'model_name': self.config.model_name,
|
| 245 |
+
'model_path': self.config.model_path,
|
| 246 |
+
'max_length': self.config.max_length,
|
| 247 |
+
'temperature': self.config.temperature,
|
| 248 |
+
'num_samples': len(dataset)
|
| 249 |
+
},
|
| 250 |
+
'metrics': metrics,
|
| 251 |
+
'timing': {
|
| 252 |
+
'total_time': total_time,
|
| 253 |
+
'avg_time_per_example': total_time / len(dataset),
|
| 254 |
+
'throughput': len(dataset) / total_time
|
| 255 |
+
},
|
| 256 |
+
'examples': results
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
# Save results
|
| 260 |
+
if self.config.save_results:
|
| 261 |
+
self.save_results(evaluation_results)
|
| 262 |
+
|
| 263 |
+
print(f"\n✅ Evaluation complete!")
|
| 264 |
+
print(f"Total time: {total_time:.2f}s")
|
| 265 |
+
print(f"Avg time per example: {total_time/len(dataset):.2f}s")
|
| 266 |
+
if metrics:
|
| 267 |
+
print(f"\nMetrics:")
|
| 268 |
+
for key, value in metrics.items():
|
| 269 |
+
if isinstance(value, (int, float)):
|
| 270 |
+
print(f" {key}: {value:.2f}")
|
| 271 |
+
|
| 272 |
+
return evaluation_results
|
| 273 |
+
|
| 274 |
+
def save_results(self, results: Dict[str, Any], filename: Optional[str] = None):
|
| 275 |
+
"""
|
| 276 |
+
Save evaluation results to JSON.
|
| 277 |
+
|
| 278 |
+
Args:
|
| 279 |
+
results: Evaluation results
|
| 280 |
+
filename: Output filename
|
| 281 |
+
"""
|
| 282 |
+
output_dir = Path(self.config.output_dir)
|
| 283 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 284 |
+
|
| 285 |
+
if filename is None:
|
| 286 |
+
timestamp = time.strftime('%Y%m%d_%H%M%S')
|
| 287 |
+
filename = f"evaluation_{timestamp}.json"
|
| 288 |
+
|
| 289 |
+
filepath = output_dir / filename
|
| 290 |
+
|
| 291 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 292 |
+
json.dump(results, f, indent=2, ensure_ascii=False)
|
| 293 |
+
|
| 294 |
+
print(f"Results saved to: {filepath}")
|
| 295 |
+
|
| 296 |
+
def load_results(self, filepath: str) -> Dict[str, Any]:
|
| 297 |
+
"""
|
| 298 |
+
Load evaluation results from JSON.
|
| 299 |
+
|
| 300 |
+
Args:
|
| 301 |
+
filepath: Path to results file
|
| 302 |
+
|
| 303 |
+
Returns:
|
| 304 |
+
Loaded results
|
| 305 |
+
"""
|
| 306 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 307 |
+
results = json.load(f)
|
| 308 |
+
|
| 309 |
+
return results
|
| 310 |
+
|
| 311 |
+
def compare_results(self, results_list: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 312 |
+
"""
|
| 313 |
+
Compare multiple evaluation results.
|
| 314 |
+
|
| 315 |
+
Args:
|
| 316 |
+
results_list: List of evaluation results
|
| 317 |
+
|
| 318 |
+
Returns:
|
| 319 |
+
Comparison summary
|
| 320 |
+
"""
|
| 321 |
+
comparison = {
|
| 322 |
+
'num_evaluations': len(results_list),
|
| 323 |
+
'models': [r['config']['model_name'] for r in results_list],
|
| 324 |
+
'metrics_comparison': {}
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
# Extract metrics
|
| 328 |
+
all_metrics = {}
|
| 329 |
+
for i, results in enumerate(results_list):
|
| 330 |
+
model_name = results['config']['model_name']
|
| 331 |
+
all_metrics[model_name] = results.get('metrics', {})
|
| 332 |
+
|
| 333 |
+
# Compare each metric
|
| 334 |
+
metric_names = set()
|
| 335 |
+
for metrics in all_metrics.values():
|
| 336 |
+
metric_names.update(metrics.keys())
|
| 337 |
+
|
| 338 |
+
for metric in metric_names:
|
| 339 |
+
values = {}
|
| 340 |
+
for model, metrics in all_metrics.items():
|
| 341 |
+
if metric in metrics:
|
| 342 |
+
values[model] = metrics[metric]
|
| 343 |
+
|
| 344 |
+
if values:
|
| 345 |
+
comparison['metrics_comparison'][metric] = values
|
| 346 |
+
|
| 347 |
+
return comparison
|
evaluation/gap_analyzer.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gap Analyzer Module
|
| 3 |
+
|
| 4 |
+
Analyzes model performance to identify knowledge gaps and weak areas.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from typing import List, Dict, Optional, Any, Tuple
|
| 8 |
+
import json
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from collections import defaultdict
|
| 11 |
+
import statistics
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class GapAnalyzer:
|
| 15 |
+
"""
|
| 16 |
+
Analyzes evaluation results to identify knowledge gaps.
|
| 17 |
+
|
| 18 |
+
Features:
|
| 19 |
+
- Topic-level performance analysis
|
| 20 |
+
- Trend tracking across evaluations
|
| 21 |
+
- Weakness identification
|
| 22 |
+
- Strength identification
|
| 23 |
+
- Improvement recommendations
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
def __init__(self):
|
| 27 |
+
"""Initialize gap analyzer."""
|
| 28 |
+
self.evaluation_history: List[Dict[str, Any]] = []
|
| 29 |
+
self.performance_by_category: Dict[str, List[float]] = defaultdict(list)
|
| 30 |
+
self.gaps: List[Dict[str, Any]] = []
|
| 31 |
+
|
| 32 |
+
def add_evaluation_results(self, results: Dict[str, Any]):
|
| 33 |
+
"""
|
| 34 |
+
Add evaluation results for analysis.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
results: Evaluation results dictionary
|
| 38 |
+
"""
|
| 39 |
+
self.evaluation_history.append(results)
|
| 40 |
+
|
| 41 |
+
# Extract category performance if available
|
| 42 |
+
if 'examples' in results:
|
| 43 |
+
category_scores = defaultdict(list)
|
| 44 |
+
|
| 45 |
+
for example in results['examples']:
|
| 46 |
+
category = example.get('category', 'general')
|
| 47 |
+
|
| 48 |
+
# Calculate score for this example
|
| 49 |
+
prediction = example.get('prediction', '').lower()
|
| 50 |
+
reference = example.get('reference', '').lower()
|
| 51 |
+
|
| 52 |
+
# Simple scoring: 1 if similar, 0 otherwise
|
| 53 |
+
score = 1.0 if self._calculate_similarity(prediction, reference) > 0.5 else 0.0
|
| 54 |
+
category_scores[category].append(score)
|
| 55 |
+
|
| 56 |
+
# Store average scores by category
|
| 57 |
+
for category, scores in category_scores.items():
|
| 58 |
+
avg_score = (sum(scores) / len(scores)) * 100 if scores else 0
|
| 59 |
+
self.performance_by_category[category].append(avg_score)
|
| 60 |
+
|
| 61 |
+
def _calculate_similarity(self, text1: str, text2: str) -> float:
|
| 62 |
+
"""Calculate simple similarity between two texts."""
|
| 63 |
+
words1 = set(text1.split())
|
| 64 |
+
words2 = set(text2.split())
|
| 65 |
+
|
| 66 |
+
if not words1 or not words2:
|
| 67 |
+
return 0.0
|
| 68 |
+
|
| 69 |
+
intersection = words1 & words2
|
| 70 |
+
union = words1 | words2
|
| 71 |
+
|
| 72 |
+
return len(intersection) / len(union) if union else 0.0
|
| 73 |
+
|
| 74 |
+
def analyze_gaps(
|
| 75 |
+
self,
|
| 76 |
+
weak_threshold: float = 60.0,
|
| 77 |
+
strong_threshold: float = 85.0
|
| 78 |
+
) -> List[Dict[str, Any]]:
|
| 79 |
+
"""
|
| 80 |
+
Analyze performance and identify gaps.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
weak_threshold: Score below this is considered weak
|
| 84 |
+
strong_threshold: Score above this is considered strong
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
List of identified gaps with details
|
| 88 |
+
"""
|
| 89 |
+
gaps = []
|
| 90 |
+
|
| 91 |
+
# Analyze each category
|
| 92 |
+
for category, scores in self.performance_by_category.items():
|
| 93 |
+
if not scores:
|
| 94 |
+
continue
|
| 95 |
+
|
| 96 |
+
avg_score = statistics.mean(scores)
|
| 97 |
+
latest_score = scores[-1] if scores else 0
|
| 98 |
+
|
| 99 |
+
# Calculate trend
|
| 100 |
+
trend = "stable"
|
| 101 |
+
if len(scores) >= 2:
|
| 102 |
+
recent_avg = statistics.mean(scores[-3:]) if len(scores) >= 3 else statistics.mean(scores[-2:])
|
| 103 |
+
older_avg = statistics.mean(scores[:-3]) if len(scores) >= 3 else scores[0]
|
| 104 |
+
|
| 105 |
+
if recent_avg > older_avg + 5:
|
| 106 |
+
trend = "improving"
|
| 107 |
+
elif recent_avg < older_avg - 5:
|
| 108 |
+
trend = "declining"
|
| 109 |
+
|
| 110 |
+
# Classify performance level
|
| 111 |
+
if avg_score < weak_threshold:
|
| 112 |
+
level = "WEAK"
|
| 113 |
+
priority = "HIGH"
|
| 114 |
+
elif avg_score < strong_threshold:
|
| 115 |
+
level = "MODERATE"
|
| 116 |
+
priority = "MEDIUM"
|
| 117 |
+
else:
|
| 118 |
+
level = "STRONG"
|
| 119 |
+
priority = "LOW"
|
| 120 |
+
|
| 121 |
+
gap = {
|
| 122 |
+
'category': category,
|
| 123 |
+
'avg_score': avg_score,
|
| 124 |
+
'latest_score': latest_score,
|
| 125 |
+
'num_evaluations': len(scores),
|
| 126 |
+
'trend': trend,
|
| 127 |
+
'level': level,
|
| 128 |
+
'priority': priority,
|
| 129 |
+
'scores_history': scores
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
gaps.append(gap)
|
| 133 |
+
|
| 134 |
+
# Sort by priority (weak areas first)
|
| 135 |
+
priority_order = {'HIGH': 0, 'MEDIUM': 1, 'LOW': 2}
|
| 136 |
+
gaps.sort(key=lambda x: (priority_order.get(x['priority'], 3), x['avg_score']))
|
| 137 |
+
|
| 138 |
+
self.gaps = gaps
|
| 139 |
+
return gaps
|
| 140 |
+
|
| 141 |
+
def get_weakest_topics(self, n: int = 5) -> List[Dict[str, Any]]:
|
| 142 |
+
"""
|
| 143 |
+
Get the N weakest topics.
|
| 144 |
+
|
| 145 |
+
Args:
|
| 146 |
+
n: Number of topics to return
|
| 147 |
+
|
| 148 |
+
Returns:
|
| 149 |
+
List of weakest topics
|
| 150 |
+
"""
|
| 151 |
+
if not self.gaps:
|
| 152 |
+
self.analyze_gaps()
|
| 153 |
+
|
| 154 |
+
weak_gaps = [g for g in self.gaps if g['level'] in ['WEAK', 'MODERATE']]
|
| 155 |
+
return weak_gaps[:n]
|
| 156 |
+
|
| 157 |
+
def get_strongest_topics(self, n: int = 5) -> List[Dict[str, Any]]:
|
| 158 |
+
"""
|
| 159 |
+
Get the N strongest topics.
|
| 160 |
+
|
| 161 |
+
Args:
|
| 162 |
+
n: Number of topics to return
|
| 163 |
+
|
| 164 |
+
Returns:
|
| 165 |
+
List of strongest topics
|
| 166 |
+
"""
|
| 167 |
+
if not self.gaps:
|
| 168 |
+
self.analyze_gaps()
|
| 169 |
+
|
| 170 |
+
strong_gaps = [g for g in self.gaps if g['level'] == 'STRONG']
|
| 171 |
+
return strong_gaps[:n]
|
| 172 |
+
|
| 173 |
+
def get_declining_topics(self) -> List[Dict[str, Any]]:
|
| 174 |
+
"""Get topics with declining performance."""
|
| 175 |
+
if not self.gaps:
|
| 176 |
+
self.analyze_gaps()
|
| 177 |
+
|
| 178 |
+
return [g for g in self.gaps if g['trend'] == 'declining']
|
| 179 |
+
|
| 180 |
+
def get_improving_topics(self) -> List[Dict[str, Any]]:
|
| 181 |
+
"""Get topics with improving performance."""
|
| 182 |
+
if not self.gaps:
|
| 183 |
+
self.analyze_gaps()
|
| 184 |
+
|
| 185 |
+
return [g for g in self.gaps if g['trend'] == 'improving']
|
| 186 |
+
|
| 187 |
+
def generate_gap_report(self) -> str:
|
| 188 |
+
"""
|
| 189 |
+
Generate a human-readable gap analysis report.
|
| 190 |
+
|
| 191 |
+
Returns:
|
| 192 |
+
Formatted report string
|
| 193 |
+
"""
|
| 194 |
+
if not self.gaps:
|
| 195 |
+
self.analyze_gaps()
|
| 196 |
+
|
| 197 |
+
report = ["=" * 80]
|
| 198 |
+
report.append("KNOWLEDGE GAP ANALYSIS REPORT")
|
| 199 |
+
report.append("=" * 80)
|
| 200 |
+
report.append("")
|
| 201 |
+
|
| 202 |
+
# Overall summary
|
| 203 |
+
weak_count = sum(1 for g in self.gaps if g['level'] == 'WEAK')
|
| 204 |
+
moderate_count = sum(1 for g in self.gaps if g['level'] == 'MODERATE')
|
| 205 |
+
strong_count = sum(1 for g in self.gaps if g['level'] == 'STRONG')
|
| 206 |
+
|
| 207 |
+
report.append(f"Total Categories Analyzed: {len(self.gaps)}")
|
| 208 |
+
report.append(f" - WEAK (needs immediate attention): {weak_count}")
|
| 209 |
+
report.append(f" - MODERATE (needs improvement): {moderate_count}")
|
| 210 |
+
report.append(f" - STRONG (performing well): {strong_count}")
|
| 211 |
+
report.append("")
|
| 212 |
+
|
| 213 |
+
# Weak areas (priority)
|
| 214 |
+
weak_topics = [g for g in self.gaps if g['level'] == 'WEAK']
|
| 215 |
+
if weak_topics:
|
| 216 |
+
report.append("🔴 WEAK AREAS (Priority Training Needed):")
|
| 217 |
+
report.append("-" * 80)
|
| 218 |
+
for gap in weak_topics:
|
| 219 |
+
report.append(f" • {gap['category']}: {gap['avg_score']:.1f}% (Trend: {gap['trend']})")
|
| 220 |
+
report.append("")
|
| 221 |
+
|
| 222 |
+
# Moderate areas
|
| 223 |
+
moderate_topics = [g for g in self.gaps if g['level'] == 'MODERATE']
|
| 224 |
+
if moderate_topics:
|
| 225 |
+
report.append("🟡 MODERATE AREAS (Recommended Improvement):")
|
| 226 |
+
report.append("-" * 80)
|
| 227 |
+
for gap in moderate_topics[:5]: # Top 5
|
| 228 |
+
report.append(f" • {gap['category']}: {gap['avg_score']:.1f}% (Trend: {gap['trend']})")
|
| 229 |
+
report.append("")
|
| 230 |
+
|
| 231 |
+
# Strong areas
|
| 232 |
+
strong_topics = [g for g in self.gaps if g['level'] == 'STRONG']
|
| 233 |
+
if strong_topics:
|
| 234 |
+
report.append("🟢 STRONG AREAS (Excellent Performance):")
|
| 235 |
+
report.append("-" * 80)
|
| 236 |
+
for gap in strong_topics[:5]: # Top 5
|
| 237 |
+
report.append(f" • {gap['category']}: {gap['avg_score']:.1f}% (Trend: {gap['trend']})")
|
| 238 |
+
report.append("")
|
| 239 |
+
|
| 240 |
+
# Trends
|
| 241 |
+
declining = self.get_declining_topics()
|
| 242 |
+
improving = self.get_improving_topics()
|
| 243 |
+
|
| 244 |
+
if declining:
|
| 245 |
+
report.append("📉 DECLINING PERFORMANCE (Needs Attention):")
|
| 246 |
+
report.append("-" * 80)
|
| 247 |
+
for gap in declining:
|
| 248 |
+
report.append(f" • {gap['category']}: {gap['avg_score']:.1f}%")
|
| 249 |
+
report.append("")
|
| 250 |
+
|
| 251 |
+
if improving:
|
| 252 |
+
report.append("📈 IMPROVING PERFORMANCE (Keep It Up!):")
|
| 253 |
+
report.append("-" * 80)
|
| 254 |
+
for gap in improving:
|
| 255 |
+
report.append(f" • {gap['category']}: {gap['avg_score']:.1f}%")
|
| 256 |
+
report.append("")
|
| 257 |
+
|
| 258 |
+
report.append("=" * 80)
|
| 259 |
+
|
| 260 |
+
return "\n".join(report)
|
| 261 |
+
|
| 262 |
+
def get_performance_summary(self) -> Dict[str, Any]:
|
| 263 |
+
"""
|
| 264 |
+
Get overall performance summary.
|
| 265 |
+
|
| 266 |
+
Returns:
|
| 267 |
+
Summary statistics
|
| 268 |
+
"""
|
| 269 |
+
if not self.gaps:
|
| 270 |
+
self.analyze_gaps()
|
| 271 |
+
|
| 272 |
+
all_scores = [g['avg_score'] for g in self.gaps]
|
| 273 |
+
|
| 274 |
+
summary = {
|
| 275 |
+
'num_categories': len(self.gaps),
|
| 276 |
+
'overall_avg_score': statistics.mean(all_scores) if all_scores else 0,
|
| 277 |
+
'min_score': min(all_scores) if all_scores else 0,
|
| 278 |
+
'max_score': max(all_scores) if all_scores else 0,
|
| 279 |
+
'weak_count': sum(1 for g in self.gaps if g['level'] == 'WEAK'),
|
| 280 |
+
'moderate_count': sum(1 for g in self.gaps if g['level'] == 'MODERATE'),
|
| 281 |
+
'strong_count': sum(1 for g in self.gaps if g['level'] == 'STRONG'),
|
| 282 |
+
'declining_count': sum(1 for g in self.gaps if g['trend'] == 'declining'),
|
| 283 |
+
'improving_count': sum(1 for g in self.gaps if g['trend'] == 'improving')
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
return summary
|
| 287 |
+
|
| 288 |
+
def export_gaps(self, filepath: str):
|
| 289 |
+
"""
|
| 290 |
+
Export gap analysis to JSON file.
|
| 291 |
+
|
| 292 |
+
Args:
|
| 293 |
+
filepath: Output file path
|
| 294 |
+
"""
|
| 295 |
+
if not self.gaps:
|
| 296 |
+
self.analyze_gaps()
|
| 297 |
+
|
| 298 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 299 |
+
|
| 300 |
+
data = {
|
| 301 |
+
'summary': self.get_performance_summary(),
|
| 302 |
+
'gaps': self.gaps,
|
| 303 |
+
'report': self.generate_gap_report()
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 307 |
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 308 |
+
|
| 309 |
+
print(f"Gap analysis exported to: {filepath}")
|
| 310 |
+
|
| 311 |
+
def load_gaps(self, filepath: str):
|
| 312 |
+
"""
|
| 313 |
+
Load gap analysis from JSON file.
|
| 314 |
+
|
| 315 |
+
Args:
|
| 316 |
+
filepath: Input file path
|
| 317 |
+
"""
|
| 318 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 319 |
+
data = json.load(f)
|
| 320 |
+
|
| 321 |
+
self.gaps = data.get('gaps', [])
|
| 322 |
+
|
| 323 |
+
# Reconstruct performance_by_category
|
| 324 |
+
for gap in self.gaps:
|
| 325 |
+
category = gap['category']
|
| 326 |
+
scores = gap.get('scores_history', [])
|
| 327 |
+
self.performance_by_category[category] = scores
|
| 328 |
+
|
| 329 |
+
def compare_evaluations(
|
| 330 |
+
self,
|
| 331 |
+
eval1: Dict[str, Any],
|
| 332 |
+
eval2: Dict[str, Any]
|
| 333 |
+
) -> Dict[str, Any]:
|
| 334 |
+
"""
|
| 335 |
+
Compare two evaluation results.
|
| 336 |
+
|
| 337 |
+
Args:
|
| 338 |
+
eval1: First evaluation results
|
| 339 |
+
eval2: Second evaluation results
|
| 340 |
+
|
| 341 |
+
Returns:
|
| 342 |
+
Comparison details
|
| 343 |
+
"""
|
| 344 |
+
comparison = {
|
| 345 |
+
'improvement': {},
|
| 346 |
+
'decline': {},
|
| 347 |
+
'stable': {}
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
# Extract metrics from both
|
| 351 |
+
metrics1 = eval1.get('metrics', {})
|
| 352 |
+
metrics2 = eval2.get('metrics', {})
|
| 353 |
+
|
| 354 |
+
# Compare each metric
|
| 355 |
+
for metric in set(metrics1.keys()) | set(metrics2.keys()):
|
| 356 |
+
if metric in metrics1 and metric in metrics2:
|
| 357 |
+
val1 = metrics1[metric]
|
| 358 |
+
val2 = metrics2[metric]
|
| 359 |
+
|
| 360 |
+
if isinstance(val1, (int, float)) and isinstance(val2, (int, float)):
|
| 361 |
+
diff = val2 - val1
|
| 362 |
+
percent_change = (diff / val1 * 100) if val1 != 0 else 0
|
| 363 |
+
|
| 364 |
+
if diff > 1: # Improved
|
| 365 |
+
comparison['improvement'][metric] = {
|
| 366 |
+
'old': val1,
|
| 367 |
+
'new': val2,
|
| 368 |
+
'change': diff,
|
| 369 |
+
'percent_change': percent_change
|
| 370 |
+
}
|
| 371 |
+
elif diff < -1: # Declined
|
| 372 |
+
comparison['decline'][metric] = {
|
| 373 |
+
'old': val1,
|
| 374 |
+
'new': val2,
|
| 375 |
+
'change': diff,
|
| 376 |
+
'percent_change': percent_change
|
| 377 |
+
}
|
| 378 |
+
else: # Stable
|
| 379 |
+
comparison['stable'][metric] = {
|
| 380 |
+
'old': val1,
|
| 381 |
+
'new': val2,
|
| 382 |
+
'change': diff
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
return comparison
|
| 386 |
+
|
| 387 |
+
def get_category_details(self, category: str) -> Optional[Dict[str, Any]]:
|
| 388 |
+
"""
|
| 389 |
+
Get detailed analysis for a specific category.
|
| 390 |
+
|
| 391 |
+
Args:
|
| 392 |
+
category: Category name
|
| 393 |
+
|
| 394 |
+
Returns:
|
| 395 |
+
Category details or None if not found
|
| 396 |
+
"""
|
| 397 |
+
if not self.gaps:
|
| 398 |
+
self.analyze_gaps()
|
| 399 |
+
|
| 400 |
+
for gap in self.gaps:
|
| 401 |
+
if gap['category'] == category:
|
| 402 |
+
return gap
|
| 403 |
+
|
| 404 |
+
return None
|
evaluation/intelligent_benchmark_scraper.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Intelligent Benchmark & Exam Scraper
|
| 3 |
+
|
| 4 |
+
Scrapes the web to find domain-specific questions, scenarios, and test content.
|
| 5 |
+
Automatically builds comprehensive benchmarks for any use case.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import re
|
| 9 |
+
import json
|
| 10 |
+
import requests
|
| 11 |
+
from typing import List, Dict, Optional
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
import time
|
| 14 |
+
from bs4 import BeautifulSoup
|
| 15 |
+
from urllib.parse import quote_plus
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class IntelligentBenchmarkScraper:
|
| 19 |
+
"""
|
| 20 |
+
Scrapes web sources to build domain-specific benchmarks and exams.
|
| 21 |
+
|
| 22 |
+
Features:
|
| 23 |
+
- Web search for relevant content
|
| 24 |
+
- Multi-source scraping (Wikipedia, educational sites, forums, documentation)
|
| 25 |
+
- Question extraction and generation
|
| 26 |
+
- Quality scoring and filtering
|
| 27 |
+
- Benchmark formatting
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, api_key: Optional[str] = None):
|
| 31 |
+
"""
|
| 32 |
+
Initialize scraper.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
api_key: OpenAI/Anthropic key for question generation from scraped content
|
| 36 |
+
"""
|
| 37 |
+
self.api_key = api_key
|
| 38 |
+
self.session = requests.Session()
|
| 39 |
+
self.session.headers.update({
|
| 40 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
| 41 |
+
})
|
| 42 |
+
|
| 43 |
+
def search_web(self, query: str, num_results: int = 10) -> List[Dict]:
|
| 44 |
+
"""
|
| 45 |
+
Search the web for relevant content using DuckDuckGo (no API key needed).
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
query: Search query
|
| 49 |
+
num_results: Number of results to return
|
| 50 |
+
|
| 51 |
+
Returns:
|
| 52 |
+
List of search results with title, URL, snippet
|
| 53 |
+
"""
|
| 54 |
+
results = []
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
# Use DuckDuckGo HTML search (no API required)
|
| 58 |
+
search_url = f"https://html.duckduckgo.com/html/?q={quote_plus(query)}"
|
| 59 |
+
|
| 60 |
+
response = self.session.get(search_url, timeout=10)
|
| 61 |
+
response.raise_for_status()
|
| 62 |
+
|
| 63 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 64 |
+
|
| 65 |
+
# Parse results
|
| 66 |
+
for result_div in soup.find_all('div', class_='result')[:num_results]:
|
| 67 |
+
title_elem = result_div.find('a', class_='result__a')
|
| 68 |
+
snippet_elem = result_div.find('a', class_='result__snippet')
|
| 69 |
+
|
| 70 |
+
if title_elem and snippet_elem:
|
| 71 |
+
results.append({
|
| 72 |
+
'title': title_elem.get_text(strip=True),
|
| 73 |
+
'url': title_elem['href'],
|
| 74 |
+
'snippet': snippet_elem.get_text(strip=True)
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
except Exception as e:
|
| 78 |
+
print(f"Search error: {e}")
|
| 79 |
+
# Fallback: provide some generic sources
|
| 80 |
+
results = self._get_fallback_sources(query)
|
| 81 |
+
|
| 82 |
+
return results
|
| 83 |
+
|
| 84 |
+
def _get_fallback_sources(self, query: str) -> List[Dict]:
|
| 85 |
+
"""Provide fallback educational sources when search fails."""
|
| 86 |
+
domain_keywords = query.lower()
|
| 87 |
+
|
| 88 |
+
sources = []
|
| 89 |
+
|
| 90 |
+
# Wikipedia
|
| 91 |
+
wiki_topic = query.replace(' ', '_')
|
| 92 |
+
sources.append({
|
| 93 |
+
'title': f"Wikipedia: {query}",
|
| 94 |
+
'url': f"https://en.wikipedia.org/wiki/{wiki_topic}",
|
| 95 |
+
'snippet': f"Comprehensive overview of {query}"
|
| 96 |
+
})
|
| 97 |
+
|
| 98 |
+
# Add domain-specific sources
|
| 99 |
+
if 'financial' in domain_keywords or 'finance' in domain_keywords:
|
| 100 |
+
sources.extend([
|
| 101 |
+
{
|
| 102 |
+
'title': "Investopedia: Financial Certification Exams",
|
| 103 |
+
'url': "https://www.investopedia.com/",
|
| 104 |
+
'snippet': "Financial education and exam prep"
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
'title': "CFP Board Practice Questions",
|
| 108 |
+
'url': "https://www.cfp.net/",
|
| 109 |
+
'snippet': "CFP certification resources"
|
| 110 |
+
}
|
| 111 |
+
])
|
| 112 |
+
elif 'medical' in domain_keywords or 'health' in domain_keywords:
|
| 113 |
+
sources.extend([
|
| 114 |
+
{
|
| 115 |
+
'title': "NCBI Medical Resources",
|
| 116 |
+
'url': "https://www.ncbi.nlm.nih.gov/",
|
| 117 |
+
'snippet': "Medical knowledge base"
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
'title': "MedlinePlus Health Topics",
|
| 121 |
+
'url': "https://medlineplus.gov/",
|
| 122 |
+
'snippet': "Consumer health information"
|
| 123 |
+
}
|
| 124 |
+
])
|
| 125 |
+
elif 'legal' in domain_keywords or 'law' in domain_keywords:
|
| 126 |
+
sources.extend([
|
| 127 |
+
{
|
| 128 |
+
'title': "Cornell Legal Information Institute",
|
| 129 |
+
'url': "https://www.law.cornell.edu/",
|
| 130 |
+
'snippet': "Free legal resources and case law"
|
| 131 |
+
}
|
| 132 |
+
])
|
| 133 |
+
|
| 134 |
+
return sources
|
| 135 |
+
|
| 136 |
+
def scrape_content(self, url: str) -> str:
|
| 137 |
+
"""
|
| 138 |
+
Scrape text content from a URL.
|
| 139 |
+
|
| 140 |
+
Args:
|
| 141 |
+
url: URL to scrape
|
| 142 |
+
|
| 143 |
+
Returns:
|
| 144 |
+
Extracted text content
|
| 145 |
+
"""
|
| 146 |
+
try:
|
| 147 |
+
response = self.session.get(url, timeout=15)
|
| 148 |
+
response.raise_for_status()
|
| 149 |
+
|
| 150 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
| 151 |
+
|
| 152 |
+
# Remove script and style elements
|
| 153 |
+
for script in soup(['script', 'style', 'header', 'footer', 'nav']):
|
| 154 |
+
script.decompose()
|
| 155 |
+
|
| 156 |
+
# Get text
|
| 157 |
+
text = soup.get_text()
|
| 158 |
+
|
| 159 |
+
# Clean up
|
| 160 |
+
lines = (line.strip() for line in text.splitlines())
|
| 161 |
+
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
|
| 162 |
+
text = ' '.join(chunk for chunk in chunks if chunk)
|
| 163 |
+
|
| 164 |
+
# Limit size
|
| 165 |
+
return text[:10000] # Max 10K chars per page
|
| 166 |
+
|
| 167 |
+
except Exception as e:
|
| 168 |
+
print(f"Scraping error for {url}: {e}")
|
| 169 |
+
return ""
|
| 170 |
+
|
| 171 |
+
def extract_questions_from_text(self, text: str, max_questions: int = 20) -> List[Dict]:
|
| 172 |
+
"""
|
| 173 |
+
Extract questions from text using pattern matching.
|
| 174 |
+
|
| 175 |
+
Args:
|
| 176 |
+
text: Text content to analyze
|
| 177 |
+
max_questions: Maximum questions to extract
|
| 178 |
+
|
| 179 |
+
Returns:
|
| 180 |
+
List of question dicts
|
| 181 |
+
"""
|
| 182 |
+
questions = []
|
| 183 |
+
|
| 184 |
+
# Pattern 1: Questions with answers
|
| 185 |
+
# Example: "What is X? Y is..."
|
| 186 |
+
qa_pattern = r'(?:^|\n)([^.!?]*\?)\s*([^.!?]+[.!?])'
|
| 187 |
+
matches = re.findall(qa_pattern, text)
|
| 188 |
+
|
| 189 |
+
for question, answer in matches[:max_questions//2]:
|
| 190 |
+
question = question.strip()
|
| 191 |
+
answer = answer.strip()
|
| 192 |
+
|
| 193 |
+
if len(question) > 20 and len(answer) > 20:
|
| 194 |
+
questions.append({
|
| 195 |
+
'question': question,
|
| 196 |
+
'answer': answer,
|
| 197 |
+
'type': 'extracted'
|
| 198 |
+
})
|
| 199 |
+
|
| 200 |
+
# Pattern 2: Numbered/bulleted questions
|
| 201 |
+
numbered_pattern = r'(?:^|\n)\s*(?:\d+[\.\)]\s*|[•\-\*]\s*)([^.!?]*\?)'
|
| 202 |
+
numbered_matches = re.findall(numbered_pattern, text)
|
| 203 |
+
|
| 204 |
+
for question in numbered_matches[:max_questions//2]:
|
| 205 |
+
question = question.strip()
|
| 206 |
+
if len(question) > 20:
|
| 207 |
+
questions.append({
|
| 208 |
+
'question': question,
|
| 209 |
+
'answer': "", # Will be generated later
|
| 210 |
+
'type': 'extracted_no_answer'
|
| 211 |
+
})
|
| 212 |
+
|
| 213 |
+
return questions[:max_questions]
|
| 214 |
+
|
| 215 |
+
def generate_questions_from_content(self, content: str, domain: str, num_questions: int = 10) -> List[Dict]:
|
| 216 |
+
"""
|
| 217 |
+
Generate questions from content using LLM.
|
| 218 |
+
|
| 219 |
+
Args:
|
| 220 |
+
content: Source content
|
| 221 |
+
domain: Domain/topic
|
| 222 |
+
num_questions: Number of questions to generate
|
| 223 |
+
|
| 224 |
+
Returns:
|
| 225 |
+
List of generated questions
|
| 226 |
+
"""
|
| 227 |
+
if not self.api_key:
|
| 228 |
+
# Fallback: use simple templates
|
| 229 |
+
return self._generate_template_questions(content, domain, num_questions)
|
| 230 |
+
|
| 231 |
+
try:
|
| 232 |
+
# Try OpenAI first
|
| 233 |
+
if self.api_key.startswith('sk-'):
|
| 234 |
+
from openai import OpenAI
|
| 235 |
+
client = OpenAI(api_key=self.api_key)
|
| 236 |
+
|
| 237 |
+
prompt = f"""Based on the following content about {domain}, generate {num_questions} test questions with answers.
|
| 238 |
+
|
| 239 |
+
Content:
|
| 240 |
+
{content[:3000]}
|
| 241 |
+
|
| 242 |
+
Format each question as JSON:
|
| 243 |
+
{{"question": "...", "answer": "...", "difficulty": "beginner|intermediate|advanced"}}
|
| 244 |
+
|
| 245 |
+
Return a JSON array of questions."""
|
| 246 |
+
|
| 247 |
+
response = client.chat.completions.create(
|
| 248 |
+
model="gpt-3.5-turbo",
|
| 249 |
+
messages=[
|
| 250 |
+
{"role": "system", "content": "You are an expert test creator."},
|
| 251 |
+
{"role": "user", "content": prompt}
|
| 252 |
+
],
|
| 253 |
+
temperature=0.7
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
# Parse JSON response
|
| 257 |
+
content_text = response.choices[0].message.content
|
| 258 |
+
json_match = re.search(r'\[.*\]', content_text, re.DOTALL)
|
| 259 |
+
if json_match:
|
| 260 |
+
questions = json.loads(json_match.group())
|
| 261 |
+
return questions
|
| 262 |
+
|
| 263 |
+
# Try Anthropic
|
| 264 |
+
elif self.api_key.startswith('sk-ant-'):
|
| 265 |
+
from anthropic import Anthropic
|
| 266 |
+
client = Anthropic(api_key=self.api_key)
|
| 267 |
+
|
| 268 |
+
prompt = f"""Based on the following content about {domain}, generate {num_questions} test questions with answers.
|
| 269 |
+
|
| 270 |
+
Content:
|
| 271 |
+
{content[:3000]}
|
| 272 |
+
|
| 273 |
+
Format each question as JSON:
|
| 274 |
+
{{"question": "...", "answer": "...", "difficulty": "beginner|intermediate|advanced"}}
|
| 275 |
+
|
| 276 |
+
Return a JSON array of questions."""
|
| 277 |
+
|
| 278 |
+
response = client.messages.create(
|
| 279 |
+
model="claude-3-5-sonnet-20241022",
|
| 280 |
+
max_tokens=2000,
|
| 281 |
+
messages=[
|
| 282 |
+
{"role": "user", "content": prompt}
|
| 283 |
+
]
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
# Parse JSON response
|
| 287 |
+
content_text = response.content[0].text
|
| 288 |
+
json_match = re.search(r'\[.*\]', content_text, re.DOTALL)
|
| 289 |
+
if json_match:
|
| 290 |
+
questions = json.loads(json_match.group())
|
| 291 |
+
return questions
|
| 292 |
+
|
| 293 |
+
except Exception as e:
|
| 294 |
+
print(f"LLM generation error: {e}")
|
| 295 |
+
|
| 296 |
+
# Fallback
|
| 297 |
+
return self._generate_template_questions(content, domain, num_questions)
|
| 298 |
+
|
| 299 |
+
def _generate_template_questions(self, content: str, domain: str, num_questions: int) -> List[Dict]:
|
| 300 |
+
"""Generate basic questions using templates when no API available."""
|
| 301 |
+
questions = []
|
| 302 |
+
|
| 303 |
+
# Extract key terms (simple approach)
|
| 304 |
+
words = content.split()
|
| 305 |
+
unique_words = list(set([w for w in words if len(w) > 5]))[:num_questions]
|
| 306 |
+
|
| 307 |
+
templates = [
|
| 308 |
+
("What is {term}?", "answer_placeholder"),
|
| 309 |
+
("Explain the concept of {term}.", "answer_placeholder"),
|
| 310 |
+
("How does {term} work in the context of {domain}?", "answer_placeholder"),
|
| 311 |
+
("What are the key aspects of {term}?", "answer_placeholder"),
|
| 312 |
+
]
|
| 313 |
+
|
| 314 |
+
for i, term in enumerate(unique_words[:num_questions]):
|
| 315 |
+
template = templates[i % len(templates)]
|
| 316 |
+
questions.append({
|
| 317 |
+
'question': template[0].format(term=term, domain=domain),
|
| 318 |
+
'answer': f"This question requires domain expertise in {domain} regarding {term}.",
|
| 319 |
+
'difficulty': 'intermediate',
|
| 320 |
+
'type': 'template_generated'
|
| 321 |
+
})
|
| 322 |
+
|
| 323 |
+
return questions
|
| 324 |
+
|
| 325 |
+
def build_benchmark(
|
| 326 |
+
self,
|
| 327 |
+
domain: str,
|
| 328 |
+
num_questions: int = 50,
|
| 329 |
+
use_llm: bool = True
|
| 330 |
+
) -> Dict:
|
| 331 |
+
"""
|
| 332 |
+
Build a comprehensive benchmark for a domain.
|
| 333 |
+
|
| 334 |
+
Args:
|
| 335 |
+
domain: Domain/topic (e.g., "financial planning", "medical diagnostics")
|
| 336 |
+
num_questions: Target number of questions
|
| 337 |
+
use_llm: Whether to use LLM for question generation
|
| 338 |
+
|
| 339 |
+
Returns:
|
| 340 |
+
Benchmark dict with questions
|
| 341 |
+
"""
|
| 342 |
+
print(f"Building benchmark for: {domain}")
|
| 343 |
+
print(f"Target questions: {num_questions}")
|
| 344 |
+
|
| 345 |
+
all_questions = []
|
| 346 |
+
|
| 347 |
+
# Step 1: Search for relevant content
|
| 348 |
+
print("\n[1/4] Searching web for content...")
|
| 349 |
+
search_queries = [
|
| 350 |
+
f"{domain} practice questions",
|
| 351 |
+
f"{domain} exam questions",
|
| 352 |
+
f"{domain} test scenarios",
|
| 353 |
+
f"{domain} certification study guide"
|
| 354 |
+
]
|
| 355 |
+
|
| 356 |
+
all_sources = []
|
| 357 |
+
for query in search_queries:
|
| 358 |
+
sources = self.search_web(query, num_results=5)
|
| 359 |
+
all_sources.extend(sources)
|
| 360 |
+
time.sleep(1) # Rate limiting
|
| 361 |
+
|
| 362 |
+
print(f"Found {len(all_sources)} sources")
|
| 363 |
+
|
| 364 |
+
# Step 2: Scrape content from sources
|
| 365 |
+
print("\n[2/4] Scraping content from sources...")
|
| 366 |
+
scraped_content = []
|
| 367 |
+
for i, source in enumerate(all_sources[:10]): # Limit to 10 sources
|
| 368 |
+
print(f" Scraping {i+1}/10: {source['title'][:50]}...")
|
| 369 |
+
content = self.scrape_content(source['url'])
|
| 370 |
+
if content:
|
| 371 |
+
scraped_content.append({
|
| 372 |
+
'url': source['url'],
|
| 373 |
+
'title': source['title'],
|
| 374 |
+
'content': content
|
| 375 |
+
})
|
| 376 |
+
time.sleep(1) # Be polite
|
| 377 |
+
|
| 378 |
+
print(f"Successfully scraped {len(scraped_content)} pages")
|
| 379 |
+
|
| 380 |
+
# Step 3: Extract existing questions
|
| 381 |
+
print("\n[3/4] Extracting questions from content...")
|
| 382 |
+
for item in scraped_content:
|
| 383 |
+
extracted = self.extract_questions_from_text(item['content'])
|
| 384 |
+
for q in extracted:
|
| 385 |
+
q['source'] = item['url']
|
| 386 |
+
q['source_title'] = item['title']
|
| 387 |
+
all_questions.extend(extracted)
|
| 388 |
+
|
| 389 |
+
print(f"Extracted {len(all_questions)} questions from sources")
|
| 390 |
+
|
| 391 |
+
# Step 4: Generate additional questions if needed
|
| 392 |
+
if use_llm and len(all_questions) < num_questions:
|
| 393 |
+
print("\n[4/4] Generating additional questions using LLM...")
|
| 394 |
+
remaining = num_questions - len(all_questions)
|
| 395 |
+
|
| 396 |
+
# Use best content for generation
|
| 397 |
+
best_content = max(scraped_content, key=lambda x: len(x['content']))['content'] if scraped_content else ""
|
| 398 |
+
|
| 399 |
+
if best_content:
|
| 400 |
+
generated = self.generate_questions_from_content(
|
| 401 |
+
best_content,
|
| 402 |
+
domain,
|
| 403 |
+
num_questions=remaining
|
| 404 |
+
)
|
| 405 |
+
all_questions.extend(generated)
|
| 406 |
+
print(f"Generated {len(generated)} additional questions")
|
| 407 |
+
|
| 408 |
+
# Build final benchmark
|
| 409 |
+
benchmark = {
|
| 410 |
+
'name': f"{domain.title()} Benchmark",
|
| 411 |
+
'domain': domain,
|
| 412 |
+
'description': f"Automatically generated benchmark for {domain} with {len(all_questions)} questions",
|
| 413 |
+
'created_at': time.strftime('%Y-%m-%d %H:%M:%S'),
|
| 414 |
+
'num_questions': len(all_questions),
|
| 415 |
+
'sources': [s['url'] for s in scraped_content],
|
| 416 |
+
'questions': all_questions[:num_questions]
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
print(f"\n[OK] Benchmark created with {len(benchmark['questions'])} questions")
|
| 420 |
+
|
| 421 |
+
return benchmark
|
| 422 |
+
|
| 423 |
+
def save_benchmark(self, benchmark: Dict, filepath: str):
|
| 424 |
+
"""Save benchmark to JSON file."""
|
| 425 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 426 |
+
|
| 427 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 428 |
+
json.dump(benchmark, f, indent=2, ensure_ascii=False)
|
| 429 |
+
|
| 430 |
+
print(f"Saved benchmark to: {filepath}")
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
def create_scraped_benchmark(domain: str, num_questions: int = 50, api_key: Optional[str] = None) -> str:
|
| 434 |
+
"""
|
| 435 |
+
Helper function to create a benchmark from web scraping.
|
| 436 |
+
|
| 437 |
+
Args:
|
| 438 |
+
domain: Domain/topic
|
| 439 |
+
num_questions: Number of questions
|
| 440 |
+
api_key: Optional API key for LLM generation
|
| 441 |
+
|
| 442 |
+
Returns:
|
| 443 |
+
Status message
|
| 444 |
+
"""
|
| 445 |
+
scraper = IntelligentBenchmarkScraper(api_key=api_key)
|
| 446 |
+
|
| 447 |
+
benchmark = scraper.build_benchmark(
|
| 448 |
+
domain=domain,
|
| 449 |
+
num_questions=num_questions,
|
| 450 |
+
use_llm=bool(api_key)
|
| 451 |
+
)
|
| 452 |
+
|
| 453 |
+
# Save
|
| 454 |
+
filename = domain.lower().replace(' ', '_')
|
| 455 |
+
filepath = f"benchmarks/{filename}_benchmark.json"
|
| 456 |
+
scraper.save_benchmark(benchmark, filepath)
|
| 457 |
+
|
| 458 |
+
return filepath, benchmark
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
if __name__ == "__main__":
|
| 462 |
+
# Test
|
| 463 |
+
import sys
|
| 464 |
+
|
| 465 |
+
domain = sys.argv[1] if len(sys.argv) > 1 else "financial planning"
|
| 466 |
+
|
| 467 |
+
scraper = IntelligentBenchmarkScraper()
|
| 468 |
+
benchmark = scraper.build_benchmark(domain, num_questions=20, use_llm=False)
|
| 469 |
+
|
| 470 |
+
print("\nSample questions:")
|
| 471 |
+
for i, q in enumerate(benchmark['questions'][:3], 1):
|
| 472 |
+
print(f"\n{i}. {q['question']}")
|
| 473 |
+
if q.get('answer'):
|
| 474 |
+
print(f" A: {q['answer'][:100]}...")
|
evaluation/metrics.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Metrics Module
|
| 3 |
+
|
| 4 |
+
Provides various evaluation metrics for model performance assessment.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
from typing import List, Dict, Optional, Union
|
| 9 |
+
import math
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Metrics:
|
| 13 |
+
"""
|
| 14 |
+
Comprehensive metrics calculator for model evaluation.
|
| 15 |
+
|
| 16 |
+
Supports:
|
| 17 |
+
- BLEU score
|
| 18 |
+
- ROUGE-L
|
| 19 |
+
- Perplexity
|
| 20 |
+
- Custom financial domain metrics
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
def __init__(self):
|
| 24 |
+
"""Initialize metrics calculator."""
|
| 25 |
+
self.results = {}
|
| 26 |
+
|
| 27 |
+
def calculate_bleu(
|
| 28 |
+
self,
|
| 29 |
+
references: List[str],
|
| 30 |
+
hypotheses: List[str],
|
| 31 |
+
max_n: int = 4
|
| 32 |
+
) -> float:
|
| 33 |
+
"""
|
| 34 |
+
Calculate BLEU score.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
references: Reference texts
|
| 38 |
+
hypotheses: Generated texts
|
| 39 |
+
max_n: Maximum n-gram size
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
BLEU score (0-100)
|
| 43 |
+
"""
|
| 44 |
+
try:
|
| 45 |
+
from nltk.translate.bleu_score import corpus_bleu, SmoothingFunction
|
| 46 |
+
|
| 47 |
+
# Tokenize
|
| 48 |
+
ref_tokens = [[ref.split()] for ref in references]
|
| 49 |
+
hyp_tokens = [hyp.split() for hyp in hypotheses]
|
| 50 |
+
|
| 51 |
+
# Calculate with smoothing
|
| 52 |
+
smoothing = SmoothingFunction()
|
| 53 |
+
score = corpus_bleu(
|
| 54 |
+
ref_tokens,
|
| 55 |
+
hyp_tokens,
|
| 56 |
+
smoothing_function=smoothing.method1
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
return score * 100.0
|
| 60 |
+
|
| 61 |
+
except ImportError:
|
| 62 |
+
# Fallback: simple word overlap
|
| 63 |
+
return self._simple_bleu(references, hypotheses)
|
| 64 |
+
|
| 65 |
+
def _simple_bleu(self, references: List[str], hypotheses: List[str]) -> float:
|
| 66 |
+
"""Simple BLEU approximation without NLTK."""
|
| 67 |
+
total_overlap = 0
|
| 68 |
+
total_length = 0
|
| 69 |
+
|
| 70 |
+
for ref, hyp in zip(references, hypotheses):
|
| 71 |
+
ref_words = set(ref.lower().split())
|
| 72 |
+
hyp_words = set(hyp.lower().split())
|
| 73 |
+
overlap = len(ref_words & hyp_words)
|
| 74 |
+
total_overlap += overlap
|
| 75 |
+
total_length += max(len(ref_words), len(hyp_words))
|
| 76 |
+
|
| 77 |
+
if total_length == 0:
|
| 78 |
+
return 0.0
|
| 79 |
+
|
| 80 |
+
return (total_overlap / total_length) * 100.0
|
| 81 |
+
|
| 82 |
+
def calculate_rouge_l(
|
| 83 |
+
self,
|
| 84 |
+
references: List[str],
|
| 85 |
+
hypotheses: List[str]
|
| 86 |
+
) -> Dict[str, float]:
|
| 87 |
+
"""
|
| 88 |
+
Calculate ROUGE-L score.
|
| 89 |
+
|
| 90 |
+
Args:
|
| 91 |
+
references: Reference texts
|
| 92 |
+
hypotheses: Generated texts
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
Dict with precision, recall, f1
|
| 96 |
+
"""
|
| 97 |
+
total_precision = 0
|
| 98 |
+
total_recall = 0
|
| 99 |
+
total_f1 = 0
|
| 100 |
+
|
| 101 |
+
for ref, hyp in zip(references, hypotheses):
|
| 102 |
+
ref_words = ref.split()
|
| 103 |
+
hyp_words = hyp.split()
|
| 104 |
+
|
| 105 |
+
# Find longest common subsequence
|
| 106 |
+
lcs_length = self._lcs_length(ref_words, hyp_words)
|
| 107 |
+
|
| 108 |
+
# Calculate metrics
|
| 109 |
+
precision = lcs_length / len(hyp_words) if len(hyp_words) > 0 else 0
|
| 110 |
+
recall = lcs_length / len(ref_words) if len(ref_words) > 0 else 0
|
| 111 |
+
f1 = (2 * precision * recall / (precision + recall)) if (precision + recall) > 0 else 0
|
| 112 |
+
|
| 113 |
+
total_precision += precision
|
| 114 |
+
total_recall += recall
|
| 115 |
+
total_f1 += f1
|
| 116 |
+
|
| 117 |
+
n = len(references)
|
| 118 |
+
return {
|
| 119 |
+
'precision': (total_precision / n) * 100.0 if n > 0 else 0.0,
|
| 120 |
+
'recall': (total_recall / n) * 100.0 if n > 0 else 0.0,
|
| 121 |
+
'f1': (total_f1 / n) * 100.0 if n > 0 else 0.0
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
def _lcs_length(self, seq1: List[str], seq2: List[str]) -> int:
|
| 125 |
+
"""Calculate longest common subsequence length."""
|
| 126 |
+
m, n = len(seq1), len(seq2)
|
| 127 |
+
dp = [[0] * (n + 1) for _ in range(m + 1)]
|
| 128 |
+
|
| 129 |
+
for i in range(1, m + 1):
|
| 130 |
+
for j in range(1, n + 1):
|
| 131 |
+
if seq1[i-1] == seq2[j-1]:
|
| 132 |
+
dp[i][j] = dp[i-1][j-1] + 1
|
| 133 |
+
else:
|
| 134 |
+
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
|
| 135 |
+
|
| 136 |
+
return dp[m][n]
|
| 137 |
+
|
| 138 |
+
def calculate_perplexity(
|
| 139 |
+
self,
|
| 140 |
+
log_probs: List[float]
|
| 141 |
+
) -> float:
|
| 142 |
+
"""
|
| 143 |
+
Calculate perplexity from log probabilities.
|
| 144 |
+
|
| 145 |
+
Args:
|
| 146 |
+
log_probs: List of log probabilities
|
| 147 |
+
|
| 148 |
+
Returns:
|
| 149 |
+
Perplexity score
|
| 150 |
+
"""
|
| 151 |
+
if not log_probs:
|
| 152 |
+
return float('inf')
|
| 153 |
+
|
| 154 |
+
avg_log_prob = sum(log_probs) / len(log_probs)
|
| 155 |
+
perplexity = math.exp(-avg_log_prob)
|
| 156 |
+
|
| 157 |
+
return perplexity
|
| 158 |
+
|
| 159 |
+
def calculate_accuracy(
|
| 160 |
+
self,
|
| 161 |
+
predictions: List[str],
|
| 162 |
+
references: List[str]
|
| 163 |
+
) -> float:
|
| 164 |
+
"""
|
| 165 |
+
Calculate exact match accuracy.
|
| 166 |
+
|
| 167 |
+
Args:
|
| 168 |
+
predictions: Predicted answers
|
| 169 |
+
references: Reference answers
|
| 170 |
+
|
| 171 |
+
Returns:
|
| 172 |
+
Accuracy percentage
|
| 173 |
+
"""
|
| 174 |
+
if not predictions or not references:
|
| 175 |
+
return 0.0
|
| 176 |
+
|
| 177 |
+
matches = sum(
|
| 178 |
+
pred.strip().lower() == ref.strip().lower()
|
| 179 |
+
for pred, ref in zip(predictions, references)
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
return (matches / len(predictions)) * 100.0
|
| 183 |
+
|
| 184 |
+
def calculate_all_metrics(
|
| 185 |
+
self,
|
| 186 |
+
predictions: List[str],
|
| 187 |
+
references: List[str],
|
| 188 |
+
log_probs: Optional[List[float]] = None
|
| 189 |
+
) -> Dict[str, float]:
|
| 190 |
+
"""
|
| 191 |
+
Calculate all available metrics.
|
| 192 |
+
|
| 193 |
+
Args:
|
| 194 |
+
predictions: Model predictions
|
| 195 |
+
references: Reference answers
|
| 196 |
+
log_probs: Optional log probabilities for perplexity
|
| 197 |
+
|
| 198 |
+
Returns:
|
| 199 |
+
Dict of all metrics
|
| 200 |
+
"""
|
| 201 |
+
metrics = {}
|
| 202 |
+
|
| 203 |
+
# BLEU
|
| 204 |
+
try:
|
| 205 |
+
metrics['bleu'] = self.calculate_bleu(references, predictions)
|
| 206 |
+
except Exception as e:
|
| 207 |
+
print(f"BLEU calculation error: {e}")
|
| 208 |
+
metrics['bleu'] = 0.0
|
| 209 |
+
|
| 210 |
+
# ROUGE-L
|
| 211 |
+
try:
|
| 212 |
+
rouge = self.calculate_rouge_l(references, predictions)
|
| 213 |
+
metrics['rouge_l_precision'] = rouge['precision']
|
| 214 |
+
metrics['rouge_l_recall'] = rouge['recall']
|
| 215 |
+
metrics['rouge_l_f1'] = rouge['f1']
|
| 216 |
+
except Exception as e:
|
| 217 |
+
print(f"ROUGE calculation error: {e}")
|
| 218 |
+
metrics['rouge_l_f1'] = 0.0
|
| 219 |
+
|
| 220 |
+
# Accuracy
|
| 221 |
+
try:
|
| 222 |
+
metrics['accuracy'] = self.calculate_accuracy(predictions, references)
|
| 223 |
+
except Exception as e:
|
| 224 |
+
print(f"Accuracy calculation error: {e}")
|
| 225 |
+
metrics['accuracy'] = 0.0
|
| 226 |
+
|
| 227 |
+
# Perplexity
|
| 228 |
+
if log_probs:
|
| 229 |
+
try:
|
| 230 |
+
metrics['perplexity'] = self.calculate_perplexity(log_probs)
|
| 231 |
+
except Exception as e:
|
| 232 |
+
print(f"Perplexity calculation error: {e}")
|
| 233 |
+
metrics['perplexity'] = float('inf')
|
| 234 |
+
|
| 235 |
+
# Average response length
|
| 236 |
+
metrics['avg_response_length'] = sum(len(p.split()) for p in predictions) / len(predictions)
|
| 237 |
+
|
| 238 |
+
return metrics
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def calculate_perplexity(log_probs: List[float]) -> float:
|
| 242 |
+
"""
|
| 243 |
+
Standalone function to calculate perplexity.
|
| 244 |
+
|
| 245 |
+
Args:
|
| 246 |
+
log_probs: List of log probabilities
|
| 247 |
+
|
| 248 |
+
Returns:
|
| 249 |
+
Perplexity score
|
| 250 |
+
"""
|
| 251 |
+
metrics = Metrics()
|
| 252 |
+
return metrics.calculate_perplexity(log_probs)
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def calculate_bleu(references: List[str], hypotheses: List[str]) -> float:
|
| 256 |
+
"""
|
| 257 |
+
Standalone function to calculate BLEU score.
|
| 258 |
+
|
| 259 |
+
Args:
|
| 260 |
+
references: Reference texts
|
| 261 |
+
hypotheses: Generated texts
|
| 262 |
+
|
| 263 |
+
Returns:
|
| 264 |
+
BLEU score (0-100)
|
| 265 |
+
"""
|
| 266 |
+
metrics = Metrics()
|
| 267 |
+
return metrics.calculate_bleu(references, hypotheses)
|
evaluation/training_recommender.py
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Training Recommender Module
|
| 3 |
+
|
| 4 |
+
Provides AI-driven recommendations for next training session based on gap analysis.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from typing import List, Dict, Optional, Any, Tuple
|
| 8 |
+
import json
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from collections import defaultdict
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TrainingRecommender:
|
| 14 |
+
"""
|
| 15 |
+
Recommends training strategies based on performance gaps.
|
| 16 |
+
|
| 17 |
+
Features:
|
| 18 |
+
- Targeted training recommendations
|
| 19 |
+
- Data generation suggestions
|
| 20 |
+
- Priority-based training plans
|
| 21 |
+
- Progress tracking
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, gap_analyzer: Optional[Any] = None):
|
| 25 |
+
"""
|
| 26 |
+
Initialize training recommender.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
gap_analyzer: GapAnalyzer instance with performance data
|
| 30 |
+
"""
|
| 31 |
+
self.gap_analyzer = gap_analyzer
|
| 32 |
+
self.recommendations: List[Dict[str, Any]] = []
|
| 33 |
+
|
| 34 |
+
def generate_recommendations(
|
| 35 |
+
self,
|
| 36 |
+
max_recommendations: int = 5,
|
| 37 |
+
focus_on_weak: bool = True
|
| 38 |
+
) -> List[Dict[str, Any]]:
|
| 39 |
+
"""
|
| 40 |
+
Generate training recommendations based on gaps.
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
max_recommendations: Maximum number of recommendations
|
| 44 |
+
focus_on_weak: Prioritize weak areas over moderate ones
|
| 45 |
+
|
| 46 |
+
Returns:
|
| 47 |
+
List of training recommendations
|
| 48 |
+
"""
|
| 49 |
+
if not self.gap_analyzer or not self.gap_analyzer.gaps:
|
| 50 |
+
return [{
|
| 51 |
+
'category': 'General',
|
| 52 |
+
'priority': 'MEDIUM',
|
| 53 |
+
'action': 'No performance data available. Start with general training.',
|
| 54 |
+
'estimated_examples': 100,
|
| 55 |
+
'topics': ['General training data']
|
| 56 |
+
}]
|
| 57 |
+
|
| 58 |
+
recommendations = []
|
| 59 |
+
|
| 60 |
+
# Get gaps to address
|
| 61 |
+
gaps = self.gap_analyzer.gaps
|
| 62 |
+
|
| 63 |
+
if focus_on_weak:
|
| 64 |
+
# Focus on weak and declining areas first
|
| 65 |
+
priority_gaps = [
|
| 66 |
+
g for g in gaps
|
| 67 |
+
if g['level'] == 'WEAK' or g['trend'] == 'declining'
|
| 68 |
+
]
|
| 69 |
+
if not priority_gaps:
|
| 70 |
+
# Fall back to moderate areas
|
| 71 |
+
priority_gaps = [g for g in gaps if g['level'] == 'MODERATE']
|
| 72 |
+
else:
|
| 73 |
+
# Include all non-strong areas
|
| 74 |
+
priority_gaps = [g for g in gaps if g['level'] != 'STRONG']
|
| 75 |
+
|
| 76 |
+
# Generate recommendations for top gaps
|
| 77 |
+
for gap in priority_gaps[:max_recommendations]:
|
| 78 |
+
recommendation = self._create_recommendation(gap)
|
| 79 |
+
recommendations.append(recommendation)
|
| 80 |
+
|
| 81 |
+
self.recommendations = recommendations
|
| 82 |
+
return recommendations
|
| 83 |
+
|
| 84 |
+
def _create_recommendation(self, gap: Dict[str, Any]) -> Dict[str, Any]:
|
| 85 |
+
"""
|
| 86 |
+
Create a detailed recommendation for a gap.
|
| 87 |
+
|
| 88 |
+
Args:
|
| 89 |
+
gap: Gap analysis data
|
| 90 |
+
|
| 91 |
+
Returns:
|
| 92 |
+
Training recommendation
|
| 93 |
+
"""
|
| 94 |
+
category = gap['category']
|
| 95 |
+
avg_score = gap['avg_score']
|
| 96 |
+
level = gap['level']
|
| 97 |
+
|
| 98 |
+
# Determine number of examples needed
|
| 99 |
+
if avg_score < 40:
|
| 100 |
+
estimated_examples = 100
|
| 101 |
+
intensity = "intensive"
|
| 102 |
+
elif avg_score < 60:
|
| 103 |
+
estimated_examples = 50
|
| 104 |
+
intensity = "moderate"
|
| 105 |
+
else:
|
| 106 |
+
estimated_examples = 25
|
| 107 |
+
intensity = "light"
|
| 108 |
+
|
| 109 |
+
# Generate specific action items
|
| 110 |
+
action_items = self._generate_action_items(category, level, gap['trend'])
|
| 111 |
+
|
| 112 |
+
# Suggest topics
|
| 113 |
+
topics = self._suggest_topics(category)
|
| 114 |
+
|
| 115 |
+
recommendation = {
|
| 116 |
+
'category': category,
|
| 117 |
+
'priority': gap['priority'],
|
| 118 |
+
'current_score': avg_score,
|
| 119 |
+
'trend': gap['trend'],
|
| 120 |
+
'intensity': intensity,
|
| 121 |
+
'estimated_examples': estimated_examples,
|
| 122 |
+
'action': f"Focus on {category} with {intensity} training",
|
| 123 |
+
'action_items': action_items,
|
| 124 |
+
'suggested_topics': topics,
|
| 125 |
+
'expected_improvement': self._estimate_improvement(avg_score, estimated_examples)
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
return recommendation
|
| 129 |
+
|
| 130 |
+
def _generate_action_items(
|
| 131 |
+
self,
|
| 132 |
+
category: str,
|
| 133 |
+
level: str,
|
| 134 |
+
trend: str
|
| 135 |
+
) -> List[str]:
|
| 136 |
+
"""
|
| 137 |
+
Generate specific action items for a category.
|
| 138 |
+
|
| 139 |
+
Args:
|
| 140 |
+
category: Category name
|
| 141 |
+
level: Performance level
|
| 142 |
+
trend: Performance trend
|
| 143 |
+
|
| 144 |
+
Returns:
|
| 145 |
+
List of action items
|
| 146 |
+
"""
|
| 147 |
+
items = []
|
| 148 |
+
|
| 149 |
+
# Base recommendations based on level
|
| 150 |
+
if level == 'WEAK':
|
| 151 |
+
items.append(f"Add 50-100 {category} examples to training data")
|
| 152 |
+
items.append(f"Review fundamental {category} concepts")
|
| 153 |
+
items.append("Include diverse question types and difficulty levels")
|
| 154 |
+
elif level == 'MODERATE':
|
| 155 |
+
items.append(f"Add 25-50 {category} examples focusing on edge cases")
|
| 156 |
+
items.append(f"Review intermediate {category} topics")
|
| 157 |
+
else:
|
| 158 |
+
items.append(f"Maintain current {category} performance with 10-20 examples")
|
| 159 |
+
|
| 160 |
+
# Add trend-specific items
|
| 161 |
+
if trend == 'declining':
|
| 162 |
+
items.append("⚠️ Address declining performance immediately")
|
| 163 |
+
items.append(f"Review recent {category} training data for quality issues")
|
| 164 |
+
elif trend == 'improving':
|
| 165 |
+
items.append("✅ Continue current training approach")
|
| 166 |
+
|
| 167 |
+
# Add testing recommendation
|
| 168 |
+
items.append(f"Test specifically on {category} after training")
|
| 169 |
+
|
| 170 |
+
return items
|
| 171 |
+
|
| 172 |
+
def _suggest_topics(self, category: str) -> List[str]:
|
| 173 |
+
"""
|
| 174 |
+
Suggest specific topics for a category.
|
| 175 |
+
|
| 176 |
+
Args:
|
| 177 |
+
category: Category name
|
| 178 |
+
|
| 179 |
+
Returns:
|
| 180 |
+
List of suggested topics
|
| 181 |
+
"""
|
| 182 |
+
# Topic suggestions by common categories
|
| 183 |
+
topic_map = {
|
| 184 |
+
'Estate Planning': [
|
| 185 |
+
'Revocable living trusts',
|
| 186 |
+
'Wills and probate',
|
| 187 |
+
'Power of attorney',
|
| 188 |
+
'Estate tax strategies',
|
| 189 |
+
'Charitable giving',
|
| 190 |
+
'Trust structures'
|
| 191 |
+
],
|
| 192 |
+
'Retirement Planning': [
|
| 193 |
+
'401(k) and IRA strategies',
|
| 194 |
+
'Required minimum distributions',
|
| 195 |
+
'Social Security optimization',
|
| 196 |
+
'Pension planning',
|
| 197 |
+
'Retirement income strategies',
|
| 198 |
+
'Healthcare in retirement'
|
| 199 |
+
],
|
| 200 |
+
'Tax Planning': [
|
| 201 |
+
'Tax-efficient investing',
|
| 202 |
+
'Capital gains strategies',
|
| 203 |
+
'Tax-loss harvesting',
|
| 204 |
+
'Deductions and credits',
|
| 205 |
+
'Alternative minimum tax',
|
| 206 |
+
'Estate and gift taxes'
|
| 207 |
+
],
|
| 208 |
+
'Investment Planning': [
|
| 209 |
+
'Asset allocation',
|
| 210 |
+
'Portfolio diversification',
|
| 211 |
+
'Risk management',
|
| 212 |
+
'Modern portfolio theory',
|
| 213 |
+
'Performance evaluation',
|
| 214 |
+
'Rebalancing strategies'
|
| 215 |
+
],
|
| 216 |
+
'Insurance Planning': [
|
| 217 |
+
'Life insurance types',
|
| 218 |
+
'Disability insurance',
|
| 219 |
+
'Long-term care insurance',
|
| 220 |
+
'Property and casualty',
|
| 221 |
+
'Umbrella policies',
|
| 222 |
+
'Insurance needs analysis'
|
| 223 |
+
],
|
| 224 |
+
'Education Planning': [
|
| 225 |
+
'529 plans',
|
| 226 |
+
'Coverdell ESAs',
|
| 227 |
+
'Financial aid strategies',
|
| 228 |
+
'Student loan planning',
|
| 229 |
+
'Education tax benefits'
|
| 230 |
+
]
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
# Return specific topics if available, otherwise generic suggestions
|
| 234 |
+
if category in topic_map:
|
| 235 |
+
return topic_map[category]
|
| 236 |
+
else:
|
| 237 |
+
return [
|
| 238 |
+
f"Fundamental {category} concepts",
|
| 239 |
+
f"Intermediate {category} topics",
|
| 240 |
+
f"Advanced {category} strategies",
|
| 241 |
+
f"{category} best practices",
|
| 242 |
+
f"Common {category} scenarios"
|
| 243 |
+
]
|
| 244 |
+
|
| 245 |
+
def _estimate_improvement(
|
| 246 |
+
self,
|
| 247 |
+
current_score: float,
|
| 248 |
+
num_examples: int
|
| 249 |
+
) -> str:
|
| 250 |
+
"""
|
| 251 |
+
Estimate expected improvement from training.
|
| 252 |
+
|
| 253 |
+
Args:
|
| 254 |
+
current_score: Current performance score
|
| 255 |
+
num_examples: Number of training examples
|
| 256 |
+
|
| 257 |
+
Returns:
|
| 258 |
+
Improvement estimate description
|
| 259 |
+
"""
|
| 260 |
+
# Simple heuristic: more examples = more improvement, diminishing returns
|
| 261 |
+
base_improvement = min(num_examples * 0.3, 30) # Max 30% improvement
|
| 262 |
+
|
| 263 |
+
# Lower scores have more room for improvement
|
| 264 |
+
if current_score < 40:
|
| 265 |
+
multiplier = 1.5
|
| 266 |
+
elif current_score < 60:
|
| 267 |
+
multiplier = 1.2
|
| 268 |
+
else:
|
| 269 |
+
multiplier = 0.8
|
| 270 |
+
|
| 271 |
+
estimated_improvement = base_improvement * multiplier
|
| 272 |
+
new_score = min(current_score + estimated_improvement, 95)
|
| 273 |
+
|
| 274 |
+
return f"+{estimated_improvement:.1f}% (to ~{new_score:.1f}%)"
|
| 275 |
+
|
| 276 |
+
def create_training_plan(
|
| 277 |
+
self,
|
| 278 |
+
priority: str = "all",
|
| 279 |
+
include_data_generation: bool = True
|
| 280 |
+
) -> Dict[str, Any]:
|
| 281 |
+
"""
|
| 282 |
+
Create a comprehensive training plan.
|
| 283 |
+
|
| 284 |
+
Args:
|
| 285 |
+
priority: Focus on "high", "medium", "low", or "all" priority items
|
| 286 |
+
include_data_generation: Include data generation instructions
|
| 287 |
+
|
| 288 |
+
Returns:
|
| 289 |
+
Training plan
|
| 290 |
+
"""
|
| 291 |
+
if not self.recommendations:
|
| 292 |
+
self.generate_recommendations()
|
| 293 |
+
|
| 294 |
+
# Filter by priority
|
| 295 |
+
if priority.upper() != "ALL":
|
| 296 |
+
filtered_recs = [
|
| 297 |
+
r for r in self.recommendations
|
| 298 |
+
if r['priority'] == priority.upper()
|
| 299 |
+
]
|
| 300 |
+
else:
|
| 301 |
+
filtered_recs = self.recommendations
|
| 302 |
+
|
| 303 |
+
# Calculate totals
|
| 304 |
+
total_examples = sum(r['estimated_examples'] for r in filtered_recs)
|
| 305 |
+
categories = [r['category'] for r in filtered_recs]
|
| 306 |
+
|
| 307 |
+
plan = {
|
| 308 |
+
'plan_name': f"Training Plan - Priority: {priority.title()}",
|
| 309 |
+
'num_focus_areas': len(filtered_recs),
|
| 310 |
+
'focus_categories': categories,
|
| 311 |
+
'total_examples_needed': total_examples,
|
| 312 |
+
'recommendations': filtered_recs,
|
| 313 |
+
'execution_steps': self._generate_execution_steps(filtered_recs),
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
if include_data_generation:
|
| 317 |
+
plan['data_generation'] = self._generate_data_instructions(filtered_recs)
|
| 318 |
+
|
| 319 |
+
return plan
|
| 320 |
+
|
| 321 |
+
def _generate_execution_steps(
|
| 322 |
+
self,
|
| 323 |
+
recommendations: List[Dict[str, Any]]
|
| 324 |
+
) -> List[str]:
|
| 325 |
+
"""Generate step-by-step execution plan."""
|
| 326 |
+
steps = [
|
| 327 |
+
"1. Review gap analysis and recommendations",
|
| 328 |
+
"2. Prepare training data:"
|
| 329 |
+
]
|
| 330 |
+
|
| 331 |
+
for i, rec in enumerate(recommendations, 1):
|
| 332 |
+
steps.append(f" {chr(96+i)}. {rec['category']}: {rec['estimated_examples']} examples")
|
| 333 |
+
|
| 334 |
+
steps.extend([
|
| 335 |
+
"3. Generate or collect training examples",
|
| 336 |
+
"4. Validate data quality (score > 60)",
|
| 337 |
+
"5. Execute training session",
|
| 338 |
+
"6. Run targeted benchmark tests",
|
| 339 |
+
"7. Analyze results and compare to previous performance",
|
| 340 |
+
"8. Iterate if needed"
|
| 341 |
+
])
|
| 342 |
+
|
| 343 |
+
return steps
|
| 344 |
+
|
| 345 |
+
def _generate_data_instructions(
|
| 346 |
+
self,
|
| 347 |
+
recommendations: List[Dict[str, Any]]
|
| 348 |
+
) -> Dict[str, Any]:
|
| 349 |
+
"""Generate data generation instructions."""
|
| 350 |
+
instructions = {
|
| 351 |
+
'method': 'synthetic_generation',
|
| 352 |
+
'by_category': {}
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
for rec in recommendations:
|
| 356 |
+
category = rec['category']
|
| 357 |
+
instructions['by_category'][category] = {
|
| 358 |
+
'num_examples': rec['estimated_examples'],
|
| 359 |
+
'difficulty': 'mixed',
|
| 360 |
+
'topics': rec['suggested_topics'],
|
| 361 |
+
'sample_prompt': f"Generate financial advisory questions about {category}, covering topics like: {', '.join(rec['suggested_topics'][:3])}"
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
return instructions
|
| 365 |
+
|
| 366 |
+
def generate_report(self) -> str:
|
| 367 |
+
"""
|
| 368 |
+
Generate human-readable training recommendations report.
|
| 369 |
+
|
| 370 |
+
Returns:
|
| 371 |
+
Formatted report
|
| 372 |
+
"""
|
| 373 |
+
if not self.recommendations:
|
| 374 |
+
self.generate_recommendations()
|
| 375 |
+
|
| 376 |
+
report = ["=" * 80]
|
| 377 |
+
report.append("TRAINING RECOMMENDATIONS REPORT")
|
| 378 |
+
report.append("=" * 80)
|
| 379 |
+
report.append("")
|
| 380 |
+
|
| 381 |
+
if not self.recommendations:
|
| 382 |
+
report.append("No recommendations available. Performance data needed.")
|
| 383 |
+
return "\n".join(report)
|
| 384 |
+
|
| 385 |
+
# Summary
|
| 386 |
+
total_examples = sum(r['estimated_examples'] for r in self.recommendations)
|
| 387 |
+
report.append(f"Total Focus Areas: {len(self.recommendations)}")
|
| 388 |
+
report.append(f"Total Training Examples Needed: {total_examples}")
|
| 389 |
+
report.append("")
|
| 390 |
+
|
| 391 |
+
# Detailed recommendations
|
| 392 |
+
report.append("RECOMMENDED TRAINING PRIORITIES:")
|
| 393 |
+
report.append("-" * 80)
|
| 394 |
+
|
| 395 |
+
for i, rec in enumerate(self.recommendations, 1):
|
| 396 |
+
priority_symbol = {
|
| 397 |
+
'HIGH': '🔴',
|
| 398 |
+
'MEDIUM': '🟡',
|
| 399 |
+
'LOW': '🟢'
|
| 400 |
+
}.get(rec['priority'], '⚪')
|
| 401 |
+
|
| 402 |
+
report.append(f"\n{i}. {priority_symbol} {rec['category']} - Priority: {rec['priority']}")
|
| 403 |
+
report.append(f" Current Score: {rec['current_score']:.1f}%")
|
| 404 |
+
report.append(f" Trend: {rec['trend']}")
|
| 405 |
+
report.append(f" Training Intensity: {rec['intensity']}")
|
| 406 |
+
report.append(f" Recommended Examples: {rec['estimated_examples']}")
|
| 407 |
+
report.append(f" Expected Improvement: {rec['expected_improvement']}")
|
| 408 |
+
report.append("")
|
| 409 |
+
report.append(" Action Items:")
|
| 410 |
+
for item in rec['action_items']:
|
| 411 |
+
report.append(f" • {item}")
|
| 412 |
+
report.append("")
|
| 413 |
+
report.append(" Suggested Topics:")
|
| 414 |
+
for topic in rec['suggested_topics'][:5]: # Top 5 topics
|
| 415 |
+
report.append(f" - {topic}")
|
| 416 |
+
|
| 417 |
+
report.append("")
|
| 418 |
+
report.append("=" * 80)
|
| 419 |
+
report.append("NEXT STEPS:")
|
| 420 |
+
report.append("")
|
| 421 |
+
report.append("1. Generate training data for priority categories")
|
| 422 |
+
report.append("2. Focus on weak/declining areas first")
|
| 423 |
+
report.append("3. Use diverse examples covering suggested topics")
|
| 424 |
+
report.append("4. Run targeted tests after training")
|
| 425 |
+
report.append("5. Track improvement and adjust strategy")
|
| 426 |
+
report.append("=" * 80)
|
| 427 |
+
|
| 428 |
+
return "\n".join(report)
|
| 429 |
+
|
| 430 |
+
def save_recommendations(self, filepath: str):
|
| 431 |
+
"""
|
| 432 |
+
Save recommendations to JSON file.
|
| 433 |
+
|
| 434 |
+
Args:
|
| 435 |
+
filepath: Output file path
|
| 436 |
+
"""
|
| 437 |
+
if not self.recommendations:
|
| 438 |
+
self.generate_recommendations()
|
| 439 |
+
|
| 440 |
+
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
|
| 441 |
+
|
| 442 |
+
data = {
|
| 443 |
+
'recommendations': self.recommendations,
|
| 444 |
+
'training_plan': self.create_training_plan(),
|
| 445 |
+
'report': self.generate_report()
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
with open(filepath, 'w', encoding='utf-8') as f:
|
| 449 |
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 450 |
+
|
| 451 |
+
print(f"Recommendations saved to: {filepath}")
|
| 452 |
+
|
| 453 |
+
def load_recommendations(self, filepath: str):
|
| 454 |
+
"""
|
| 455 |
+
Load recommendations from JSON file.
|
| 456 |
+
|
| 457 |
+
Args:
|
| 458 |
+
filepath: Input file path
|
| 459 |
+
"""
|
| 460 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
| 461 |
+
data = json.load(f)
|
| 462 |
+
|
| 463 |
+
self.recommendations = data.get('recommendations', [])
|
| 464 |
+
|
| 465 |
+
def get_quick_wins(self) -> List[Dict[str, Any]]:
|
| 466 |
+
"""
|
| 467 |
+
Identify quick wins - categories that can improve quickly.
|
| 468 |
+
|
| 469 |
+
Returns:
|
| 470 |
+
List of quick win opportunities
|
| 471 |
+
"""
|
| 472 |
+
if not self.recommendations:
|
| 473 |
+
self.generate_recommendations()
|
| 474 |
+
|
| 475 |
+
# Quick wins: moderate performance, not too many examples needed
|
| 476 |
+
quick_wins = [
|
| 477 |
+
rec for rec in self.recommendations
|
| 478 |
+
if 50 <= rec['current_score'] < 70 and rec['estimated_examples'] <= 50
|
| 479 |
+
]
|
| 480 |
+
|
| 481 |
+
return quick_wins
|
| 482 |
+
|
| 483 |
+
def prioritize_by_impact(self) -> List[Dict[str, Any]]:
|
| 484 |
+
"""
|
| 485 |
+
Sort recommendations by expected impact.
|
| 486 |
+
|
| 487 |
+
Returns:
|
| 488 |
+
Recommendations sorted by impact
|
| 489 |
+
"""
|
| 490 |
+
if not self.recommendations:
|
| 491 |
+
self.generate_recommendations()
|
| 492 |
+
|
| 493 |
+
# Calculate impact score (combination of priority and potential improvement)
|
| 494 |
+
def impact_score(rec):
|
| 495 |
+
priority_weight = {'HIGH': 3, 'MEDIUM': 2, 'LOW': 1}
|
| 496 |
+
improvement_potential = 100 - rec['current_score']
|
| 497 |
+
return priority_weight.get(rec['priority'], 1) * improvement_potential
|
| 498 |
+
|
| 499 |
+
sorted_recs = sorted(
|
| 500 |
+
self.recommendations,
|
| 501 |
+
key=impact_score,
|
| 502 |
+
reverse=True
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
return sorted_recs
|
financial_advisor_gui.py
ADDED
|
@@ -0,0 +1,2418 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Financial Advisor Training GUI
|
| 3 |
+
|
| 4 |
+
A simple, user-friendly interface for training and testing your financial advisor AI.
|
| 5 |
+
No coding required - just click buttons and see results!
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import gradio as gr
|
| 9 |
+
import json
|
| 10 |
+
import threading
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from datetime import datetime
|
| 13 |
+
import time
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
# Import training components
|
| 17 |
+
from fine_tuning import LoRATrainer, LoRAConfig
|
| 18 |
+
from data_aggregation import DatasetBuilder, JSONDataCollector
|
| 19 |
+
from data_aggregation.synthetic_generator import SyntheticDataGenerator
|
| 20 |
+
from data_aggregation.quality_validator import QualityValidator
|
| 21 |
+
from data_aggregation.hf_dataset_loader import HuggingFaceDatasetLoader
|
| 22 |
+
from secure_config import SecureConfig
|
| 23 |
+
from model_registry import get_registry
|
| 24 |
+
from ollama_integration import OllamaClient, test_financial_advisor_ollama
|
| 25 |
+
from runpod_manager import RunPodManager, DeploymentConfig, TrainingConfig
|
| 26 |
+
from runpod_client import PodInfo
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Global variables to track training state
|
| 30 |
+
training_status = {
|
| 31 |
+
"is_training": False,
|
| 32 |
+
"current_epoch": 0,
|
| 33 |
+
"total_epochs": 0,
|
| 34 |
+
"loss": 0.0,
|
| 35 |
+
"progress": 0.0,
|
| 36 |
+
"logs": []
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
trainer_instance = None
|
| 40 |
+
secure_config = SecureConfig()
|
| 41 |
+
model_registry = get_registry()
|
| 42 |
+
selected_model_id = "qwen2.5-32b" # Default model
|
| 43 |
+
|
| 44 |
+
# RunPod state
|
| 45 |
+
runpod_manager = None
|
| 46 |
+
current_pod_info = None
|
| 47 |
+
deployment_in_progress = False
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# API Key Management Functions
|
| 51 |
+
def save_api_keys(hf_token, openai_key, anthropic_key, wandb_key, runpod_key, custom_keys_json):
|
| 52 |
+
"""Save all API keys securely"""
|
| 53 |
+
try:
|
| 54 |
+
api_keys = {
|
| 55 |
+
"HUGGINGFACE_TOKEN": hf_token or "",
|
| 56 |
+
"HF_TOKEN": hf_token or "", # Alternative name
|
| 57 |
+
"OPENAI_API_KEY": openai_key or "",
|
| 58 |
+
"ANTHROPIC_API_KEY": anthropic_key or "",
|
| 59 |
+
"WANDB_API_KEY": wandb_key or "",
|
| 60 |
+
"RUNPOD_API_KEY": runpod_key or "",
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
# Parse custom keys if provided
|
| 64 |
+
if custom_keys_json and custom_keys_json.strip():
|
| 65 |
+
try:
|
| 66 |
+
custom_keys = json.loads(custom_keys_json)
|
| 67 |
+
api_keys.update(custom_keys)
|
| 68 |
+
except json.JSONDecodeError:
|
| 69 |
+
return "⚠️ Invalid JSON in custom keys. Other keys saved successfully."
|
| 70 |
+
|
| 71 |
+
# Save securely
|
| 72 |
+
secure_config.save_keys(api_keys)
|
| 73 |
+
|
| 74 |
+
# Count non-empty keys
|
| 75 |
+
saved_count = sum(1 for v in api_keys.values() if v and v.strip())
|
| 76 |
+
|
| 77 |
+
return f"✅ Successfully saved {saved_count} API keys securely!\n\nKeys are encrypted and stored in .secrets/ directory."
|
| 78 |
+
|
| 79 |
+
except Exception as e:
|
| 80 |
+
return f"❌ Error saving keys: {e}"
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def load_api_keys():
|
| 84 |
+
"""Load API keys and return masked versions for display"""
|
| 85 |
+
try:
|
| 86 |
+
keys = secure_config.load_keys()
|
| 87 |
+
|
| 88 |
+
hf_masked = secure_config.get_masked_key("HUGGINGFACE_TOKEN") or ""
|
| 89 |
+
openai_masked = secure_config.get_masked_key("OPENAI_API_KEY") or ""
|
| 90 |
+
anthropic_masked = secure_config.get_masked_key("ANTHROPIC_API_KEY") or ""
|
| 91 |
+
wandb_masked = secure_config.get_masked_key("WANDB_API_KEY") or ""
|
| 92 |
+
runpod_masked = secure_config.get_masked_key("RUNPOD_API_KEY") or ""
|
| 93 |
+
|
| 94 |
+
# Show custom keys
|
| 95 |
+
standard_keys = {"HUGGINGFACE_TOKEN", "HF_TOKEN", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "WANDB_API_KEY", "RUNPOD_API_KEY"}
|
| 96 |
+
custom_keys = {k: secure_config.get_masked_key(k) for k in keys.keys() if k not in standard_keys}
|
| 97 |
+
|
| 98 |
+
custom_json = json.dumps(custom_keys, indent=2) if custom_keys else ""
|
| 99 |
+
|
| 100 |
+
status = f"📊 **Loaded {len(keys)} API keys**\n\n"
|
| 101 |
+
if keys:
|
| 102 |
+
status += "Keys are encrypted and loaded into environment.\n"
|
| 103 |
+
status += "Masked keys shown for security."
|
| 104 |
+
else:
|
| 105 |
+
status += "⚠️ No API keys found. Please add your keys below."
|
| 106 |
+
|
| 107 |
+
return hf_masked, openai_masked, anthropic_masked, wandb_masked, runpod_masked, custom_json, status
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
return "", "", "", "", "", "", f"❌ Error loading keys: {e}"
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def clear_api_keys():
|
| 114 |
+
"""Clear all stored API keys"""
|
| 115 |
+
try:
|
| 116 |
+
secure_config.delete_keys()
|
| 117 |
+
return "✅ All API keys cleared successfully!", "", "", "", "", "", ""
|
| 118 |
+
except Exception as e:
|
| 119 |
+
return f"❌ Error clearing keys: {e}", "", "", "", "", "", ""
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def check_required_keys():
|
| 123 |
+
"""Check if required API keys are set"""
|
| 124 |
+
hf_key = secure_config.get_key("HUGGINGFACE_TOKEN")
|
| 125 |
+
|
| 126 |
+
if not hf_key:
|
| 127 |
+
return False, "⚠️ HuggingFace token required! Set it in the Settings tab."
|
| 128 |
+
|
| 129 |
+
return True, "✅ Required API keys are set"
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
# Synthetic Data Generation Functions
|
| 133 |
+
def generate_synthetic_data(api_provider, num_examples, difficulty, use_scenarios, selected_topics):
|
| 134 |
+
"""Generate synthetic training data"""
|
| 135 |
+
try:
|
| 136 |
+
# Check if API key exists
|
| 137 |
+
if api_provider == "openai":
|
| 138 |
+
api_key = secure_config.get_key("OPENAI_API_KEY")
|
| 139 |
+
if not api_key:
|
| 140 |
+
return "❌ OpenAI API key not found! Add it in the Settings tab.", ""
|
| 141 |
+
elif api_provider == "anthropic":
|
| 142 |
+
api_key = secure_config.get_key("ANTHROPIC_API_KEY")
|
| 143 |
+
if not api_key:
|
| 144 |
+
return "❌ Anthropic API key not found! Add it in the Settings tab.", ""
|
| 145 |
+
else:
|
| 146 |
+
return "❌ Invalid API provider", ""
|
| 147 |
+
|
| 148 |
+
log = f"🤖 **SYNTHETIC DATA GENERATION**\n\n"
|
| 149 |
+
log += f"Provider: {api_provider.upper()}\n"
|
| 150 |
+
log += f"Examples: {num_examples}\n"
|
| 151 |
+
log += f"Difficulty: {difficulty}\n"
|
| 152 |
+
log += f"Scenarios: {use_scenarios}\n\n"
|
| 153 |
+
|
| 154 |
+
# Initialize generator
|
| 155 |
+
generator = SyntheticDataGenerator(api_provider=api_provider)
|
| 156 |
+
|
| 157 |
+
# Parse selected topics
|
| 158 |
+
topics_list = None
|
| 159 |
+
if selected_topics and selected_topics.strip():
|
| 160 |
+
topics_list = [t.strip() for t in selected_topics.split(",")]
|
| 161 |
+
log += f"Custom topics: {topics_list}\n\n"
|
| 162 |
+
|
| 163 |
+
log += "🚀 Starting generation...\n\n"
|
| 164 |
+
|
| 165 |
+
# Generate data
|
| 166 |
+
if use_scenarios:
|
| 167 |
+
generated_data = generator.generate_with_scenarios(num_examples=num_examples)
|
| 168 |
+
else:
|
| 169 |
+
generated_data = generator.generate_examples(
|
| 170 |
+
num_examples=num_examples,
|
| 171 |
+
topics=topics_list,
|
| 172 |
+
difficulty=difficulty
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
if not generated_data:
|
| 176 |
+
return log + "\n❌ No data generated. Check API keys and try again.", ""
|
| 177 |
+
|
| 178 |
+
log += f"\n✅ Generated {len(generated_data)} raw examples!\n\n"
|
| 179 |
+
|
| 180 |
+
# Quality validation
|
| 181 |
+
log += "🔍 **QUALITY VALIDATION**\n\n"
|
| 182 |
+
validator = QualityValidator()
|
| 183 |
+
validation_results = validator.validate_batch(generated_data)
|
| 184 |
+
|
| 185 |
+
log += f"Valid: {validation_results['valid']}/{validation_results['total']} "
|
| 186 |
+
log += f"({validation_results['valid']/validation_results['total']*100:.1f}%)\n"
|
| 187 |
+
|
| 188 |
+
if validation_results['invalid'] > 0:
|
| 189 |
+
log += f"⚠️ Filtered out {validation_results['invalid']} low-quality examples\n\n"
|
| 190 |
+
log += "**Common Issues:**\n"
|
| 191 |
+
from collections import Counter
|
| 192 |
+
issue_counter = Counter(validation_results['issues'])
|
| 193 |
+
for issue, count in issue_counter.most_common(3):
|
| 194 |
+
log += f" - {issue}: {count}x\n"
|
| 195 |
+
log += "\n"
|
| 196 |
+
else:
|
| 197 |
+
log += "✅ All examples passed quality checks!\n\n"
|
| 198 |
+
|
| 199 |
+
# Check for duplicates
|
| 200 |
+
if validation_results.get('duplicates'):
|
| 201 |
+
log += f"⚠️ Found {len(validation_results['duplicates'])} duplicate questions (removed)\n\n"
|
| 202 |
+
|
| 203 |
+
# Use only valid examples
|
| 204 |
+
valid_data = validation_results['valid_examples']
|
| 205 |
+
|
| 206 |
+
if not valid_data:
|
| 207 |
+
return log + "\n❌ No valid data after quality filtering. Try again with different settings.", ""
|
| 208 |
+
|
| 209 |
+
log += f"📊 **Final Count:** {len(valid_data)} high-quality examples\n\n"
|
| 210 |
+
|
| 211 |
+
# Calculate average quality score
|
| 212 |
+
avg_score = sum(validator.get_quality_score(ex) for ex in valid_data) / len(valid_data)
|
| 213 |
+
log += f"⭐ **Average Quality Score:** {avg_score:.1f}/100\n\n"
|
| 214 |
+
|
| 215 |
+
# Load existing data
|
| 216 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 217 |
+
if Path(data_path).exists():
|
| 218 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 219 |
+
existing_data = json.load(f)
|
| 220 |
+
else:
|
| 221 |
+
existing_data = []
|
| 222 |
+
|
| 223 |
+
# Combine and save
|
| 224 |
+
combined_data = existing_data + valid_data
|
| 225 |
+
|
| 226 |
+
Path(data_path).parent.mkdir(parents=True, exist_ok=True)
|
| 227 |
+
with open(data_path, 'w', encoding='utf-8') as f:
|
| 228 |
+
json.dump(combined_data, f, indent=2, ensure_ascii=False)
|
| 229 |
+
|
| 230 |
+
log += f"💾 Saved to training data!\n"
|
| 231 |
+
log += f"Total training examples: {len(combined_data)}\n\n"
|
| 232 |
+
|
| 233 |
+
# Show preview
|
| 234 |
+
log += "**Sample Generated Q&A:**\n\n"
|
| 235 |
+
for i, example in enumerate(valid_data[:3], 1):
|
| 236 |
+
quality_score = validator.get_quality_score(example)
|
| 237 |
+
log += f"{i}. [Quality: {quality_score:.0f}/100]\n"
|
| 238 |
+
log += f" Q: {example['instruction']}\n"
|
| 239 |
+
log += f" A: {example['output'][:150]}...\n\n"
|
| 240 |
+
|
| 241 |
+
return log, f"✅ Generated {len(valid_data)} high-quality examples! Total: {len(combined_data)}"
|
| 242 |
+
|
| 243 |
+
except Exception as e:
|
| 244 |
+
import traceback
|
| 245 |
+
error_details = traceback.format_exc()
|
| 246 |
+
return f"❌ Error generating data: {e}\n\n{error_details}", f"❌ Error: {e}"
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def get_available_topics():
|
| 250 |
+
"""Get list of available financial topics"""
|
| 251 |
+
topics = [
|
| 252 |
+
"Retirement Planning",
|
| 253 |
+
"Investment Strategies",
|
| 254 |
+
"Tax Planning",
|
| 255 |
+
"Debt Management",
|
| 256 |
+
"Emergency Funds",
|
| 257 |
+
"Budgeting",
|
| 258 |
+
"Insurance",
|
| 259 |
+
"Estate Planning",
|
| 260 |
+
"College Savings",
|
| 261 |
+
"Real Estate",
|
| 262 |
+
"Stock Market",
|
| 263 |
+
"Bonds and Fixed Income",
|
| 264 |
+
"Mutual Funds and ETFs",
|
| 265 |
+
"Cryptocurrency",
|
| 266 |
+
"Financial Independence",
|
| 267 |
+
"Side Hustles",
|
| 268 |
+
"Credit Scores",
|
| 269 |
+
"Mortgages",
|
| 270 |
+
"Small Business Finance",
|
| 271 |
+
"Risk Management"
|
| 272 |
+
]
|
| 273 |
+
return "\n".join(f"• {topic}" for topic in topics)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
# HuggingFace Dataset Loading Functions
|
| 277 |
+
def list_hf_datasets():
|
| 278 |
+
"""List available HuggingFace datasets"""
|
| 279 |
+
loader = HuggingFaceDatasetLoader()
|
| 280 |
+
datasets = loader.list_available_datasets()
|
| 281 |
+
|
| 282 |
+
output = "📦 **AVAILABLE HUGGINGFACE DATASETS**\n\n"
|
| 283 |
+
|
| 284 |
+
for ds in datasets:
|
| 285 |
+
output += f"**{ds['name']}**\n"
|
| 286 |
+
output += f" Path: {ds['path']}\n"
|
| 287 |
+
output += f" Type: {ds['type']}\n"
|
| 288 |
+
output += f" Description: {ds['description']}\n\n"
|
| 289 |
+
|
| 290 |
+
return output
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def preview_hf_dataset(dataset_path):
|
| 294 |
+
"""Preview a HuggingFace dataset"""
|
| 295 |
+
if not dataset_path or not dataset_path.strip():
|
| 296 |
+
return "⚠️ Please enter a dataset path (e.g., mitulshah/transaction-categorization)"
|
| 297 |
+
|
| 298 |
+
try:
|
| 299 |
+
loader = HuggingFaceDatasetLoader()
|
| 300 |
+
preview = loader.preview_dataset(dataset_path, num_examples=3)
|
| 301 |
+
return preview
|
| 302 |
+
|
| 303 |
+
except Exception as e:
|
| 304 |
+
return f"❌ Error previewing dataset: {e}\n\nMake sure:\n- Dataset path is correct\n- You're logged in to HuggingFace (run: huggingface-cli login)\n- Dataset is publicly accessible"
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def load_hf_dataset(dataset_path, dataset_name, max_examples, split):
|
| 308 |
+
"""Load a HuggingFace dataset and add to training data"""
|
| 309 |
+
try:
|
| 310 |
+
log = "📥 **LOADING HUGGINGFACE DATASET**\n\n"
|
| 311 |
+
|
| 312 |
+
# Check if using known dataset name or custom path
|
| 313 |
+
if dataset_name and dataset_name != "Custom Path":
|
| 314 |
+
log += f"Loading known dataset: {dataset_name}\n\n"
|
| 315 |
+
loader = HuggingFaceDatasetLoader()
|
| 316 |
+
dataset_data = loader.load_dataset_by_name(
|
| 317 |
+
dataset_name,
|
| 318 |
+
split=split,
|
| 319 |
+
max_examples=int(max_examples) if max_examples else None
|
| 320 |
+
)
|
| 321 |
+
elif dataset_path and dataset_path.strip():
|
| 322 |
+
log += f"Loading custom dataset: {dataset_path}\n\n"
|
| 323 |
+
loader = HuggingFaceDatasetLoader()
|
| 324 |
+
dataset_data = loader.load_dataset_by_path(
|
| 325 |
+
dataset_path,
|
| 326 |
+
dataset_type="auto",
|
| 327 |
+
split=split,
|
| 328 |
+
max_examples=int(max_examples) if max_examples else None
|
| 329 |
+
)
|
| 330 |
+
else:
|
| 331 |
+
return "❌ Please select a dataset or enter a custom path", ""
|
| 332 |
+
|
| 333 |
+
if not dataset_data:
|
| 334 |
+
return log + "\n❌ No data loaded. Check dataset path and try again.", ""
|
| 335 |
+
|
| 336 |
+
log += f"✅ Loaded {len(dataset_data)} examples from HuggingFace\n\n"
|
| 337 |
+
|
| 338 |
+
# Quality validation
|
| 339 |
+
log += "🔍 **QUALITY VALIDATION**\n\n"
|
| 340 |
+
validator = QualityValidator()
|
| 341 |
+
validation_results = validator.validate_batch(dataset_data)
|
| 342 |
+
|
| 343 |
+
log += f"Valid: {validation_results['valid']}/{validation_results['total']} "
|
| 344 |
+
log += f"({validation_results['valid']/validation_results['total']*100:.1f}%)\n"
|
| 345 |
+
|
| 346 |
+
if validation_results['invalid'] > 0:
|
| 347 |
+
log += f"⚠️ Filtered out {validation_results['invalid']} low-quality examples\n"
|
| 348 |
+
|
| 349 |
+
# Use only valid examples
|
| 350 |
+
valid_data = validation_results['valid_examples']
|
| 351 |
+
|
| 352 |
+
if not valid_data:
|
| 353 |
+
return log + "\n❌ No valid data after quality filtering.", ""
|
| 354 |
+
|
| 355 |
+
log += f"\n📊 **Final Count:** {len(valid_data)} high-quality examples\n\n"
|
| 356 |
+
|
| 357 |
+
# Calculate average quality score
|
| 358 |
+
avg_score = sum(validator.get_quality_score(ex) for ex in valid_data) / len(valid_data)
|
| 359 |
+
log += f"⭐ **Average Quality Score:** {avg_score:.1f}/100\n\n"
|
| 360 |
+
|
| 361 |
+
# Load existing data
|
| 362 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 363 |
+
if Path(data_path).exists():
|
| 364 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 365 |
+
existing_data = json.load(f)
|
| 366 |
+
else:
|
| 367 |
+
existing_data = []
|
| 368 |
+
|
| 369 |
+
# Combine and save
|
| 370 |
+
combined_data = existing_data + valid_data
|
| 371 |
+
|
| 372 |
+
Path(data_path).parent.mkdir(parents=True, exist_ok=True)
|
| 373 |
+
with open(data_path, 'w', encoding='utf-8') as f:
|
| 374 |
+
json.dump(combined_data, f, indent=2, ensure_ascii=False)
|
| 375 |
+
|
| 376 |
+
log += f"💾 Added to training data!\n"
|
| 377 |
+
log += f"Total training examples: {len(combined_data)}\n\n"
|
| 378 |
+
|
| 379 |
+
# Show preview
|
| 380 |
+
log += "**Sample Loaded Q&A:**\n\n"
|
| 381 |
+
for i, example in enumerate(valid_data[:3], 1):
|
| 382 |
+
quality_score = validator.get_quality_score(example)
|
| 383 |
+
log += f"{i}. [Quality: {quality_score:.0f}/100]\n"
|
| 384 |
+
log += f" Q: {example['instruction'][:100]}...\n"
|
| 385 |
+
log += f" A: {example['output'][:150]}...\n\n"
|
| 386 |
+
|
| 387 |
+
return log, f"✅ Loaded {len(valid_data)} examples from HuggingFace! Total: {len(combined_data)}"
|
| 388 |
+
|
| 389 |
+
except Exception as e:
|
| 390 |
+
import traceback
|
| 391 |
+
error_details = traceback.format_exc()
|
| 392 |
+
return f"❌ Error loading dataset: {e}\n\n{error_details}", f"❌ Error: {e}"
|
| 393 |
+
|
| 394 |
+
|
| 395 |
+
def load_training_data():
|
| 396 |
+
"""Load and display current training data"""
|
| 397 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 398 |
+
|
| 399 |
+
if not Path(data_path).exists():
|
| 400 |
+
return "❌ No training data found!", 0, "", "❌ **Not Ready**: No training data found. Add examples above or use Synthetic Data/HuggingFace tabs."
|
| 401 |
+
|
| 402 |
+
try:
|
| 403 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 404 |
+
data = json.load(f)
|
| 405 |
+
|
| 406 |
+
# Format preview
|
| 407 |
+
preview = f"📊 **Total Q&A Pairs:** {len(data)}\n\n"
|
| 408 |
+
preview += "**Sample Questions:**\n"
|
| 409 |
+
for i, item in enumerate(data[:3]):
|
| 410 |
+
preview += f"\n{i+1}. {item['instruction']}\n"
|
| 411 |
+
|
| 412 |
+
# Training readiness status
|
| 413 |
+
if len(data) < 20:
|
| 414 |
+
status = f"⚠️ **Warning**: Only {len(data)} examples. Recommended minimum: 50-100 for good results."
|
| 415 |
+
elif len(data) < 50:
|
| 416 |
+
status = f"✅ **Ready**: {len(data)} examples loaded. Consider adding more for better results (recommended: 100+)."
|
| 417 |
+
else:
|
| 418 |
+
status = f"✅ **Ready for Training**: {len(data)} examples loaded and ready!\n\n**Next Step**: Go to **⚙️ Training** tab and click 'Start Training'"
|
| 419 |
+
|
| 420 |
+
return preview, len(data), json.dumps(data, indent=2), status
|
| 421 |
+
|
| 422 |
+
except Exception as e:
|
| 423 |
+
return f"❌ Error loading data: {e}", 0, "", "❌ Error loading training data"
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
def add_training_example(question, answer, context=""):
|
| 427 |
+
"""Add a new training example"""
|
| 428 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 429 |
+
|
| 430 |
+
try:
|
| 431 |
+
# Create new example
|
| 432 |
+
new_example = {
|
| 433 |
+
"instruction": question,
|
| 434 |
+
"input": context,
|
| 435 |
+
"output": answer
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
# Validate before adding
|
| 439 |
+
validator = QualityValidator()
|
| 440 |
+
is_valid, issues = validator.validate_example(new_example)
|
| 441 |
+
|
| 442 |
+
if not is_valid:
|
| 443 |
+
issues_text = "\n".join(f" - {issue}" for issue in issues)
|
| 444 |
+
return f"⚠️ Quality issues found:\n{issues_text}\n\nExample was still added, but consider improving it.", 0
|
| 445 |
+
|
| 446 |
+
# Load existing data
|
| 447 |
+
if Path(data_path).exists():
|
| 448 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 449 |
+
data = json.load(f)
|
| 450 |
+
else:
|
| 451 |
+
data = []
|
| 452 |
+
|
| 453 |
+
data.append(new_example)
|
| 454 |
+
|
| 455 |
+
# Save
|
| 456 |
+
Path(data_path).parent.mkdir(parents=True, exist_ok=True)
|
| 457 |
+
with open(data_path, 'w', encoding='utf-8') as f:
|
| 458 |
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 459 |
+
|
| 460 |
+
quality_score = validator.get_quality_score(new_example)
|
| 461 |
+
return f"✅ Added! Quality Score: {quality_score:.0f}/100\nTotal examples: {len(data)}", len(data)
|
| 462 |
+
|
| 463 |
+
except Exception as e:
|
| 464 |
+
return f"❌ Error: {e}", 0
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def validate_training_data():
|
| 468 |
+
"""Validate all training data and return report (with auto-deduplication)"""
|
| 469 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 470 |
+
|
| 471 |
+
if not Path(data_path).exists():
|
| 472 |
+
return "❌ No training data found!"
|
| 473 |
+
|
| 474 |
+
try:
|
| 475 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 476 |
+
data = json.load(f)
|
| 477 |
+
|
| 478 |
+
original_count = len(data)
|
| 479 |
+
validator = QualityValidator()
|
| 480 |
+
|
| 481 |
+
# First, check for duplicates and auto-remove
|
| 482 |
+
deduplicated_data, num_duplicates = validator.remove_duplicates(data)
|
| 483 |
+
|
| 484 |
+
# Save deduplicated data if duplicates were found
|
| 485 |
+
if num_duplicates > 0:
|
| 486 |
+
# Create backup
|
| 487 |
+
backup_path = data_path.replace('.json', '_backup.json')
|
| 488 |
+
with open(backup_path, 'w', encoding='utf-8') as f:
|
| 489 |
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 490 |
+
|
| 491 |
+
# Save deduplicated version
|
| 492 |
+
with open(data_path, 'w', encoding='utf-8') as f:
|
| 493 |
+
json.dump(deduplicated_data, f, indent=2, ensure_ascii=False)
|
| 494 |
+
|
| 495 |
+
data = deduplicated_data
|
| 496 |
+
|
| 497 |
+
# Now validate the deduplicated data
|
| 498 |
+
validation_results = validator.validate_batch(data)
|
| 499 |
+
|
| 500 |
+
# Generate report
|
| 501 |
+
report = "=" * 60 + "\n"
|
| 502 |
+
report += "QUALITY VALIDATION REPORT (WITH AUTO-DEDUPLICATION)\n"
|
| 503 |
+
report += "=" * 60 + "\n\n"
|
| 504 |
+
|
| 505 |
+
# Deduplication results
|
| 506 |
+
if num_duplicates > 0:
|
| 507 |
+
report += "🧹 AUTO-DEDUPLICATION COMPLETE!\n"
|
| 508 |
+
report += "-" * 60 + "\n"
|
| 509 |
+
report += f"Original Examples: {original_count}\n"
|
| 510 |
+
report += f"Duplicates Removed: {num_duplicates}\n"
|
| 511 |
+
report += f"Unique Examples: {len(data)}\n"
|
| 512 |
+
report += f"Backup saved to: {backup_path}\n"
|
| 513 |
+
report += "\n" + "=" * 60 + "\n\n"
|
| 514 |
+
else:
|
| 515 |
+
report += "✅ NO DUPLICATES FOUND\n"
|
| 516 |
+
report += "-" * 60 + "\n"
|
| 517 |
+
report += f"All {len(data)} examples are unique!\n"
|
| 518 |
+
report += "\n" + "=" * 60 + "\n\n"
|
| 519 |
+
|
| 520 |
+
# Quality validation results
|
| 521 |
+
report += f"Total Examples: {validation_results['total']}\n"
|
| 522 |
+
report += f"Valid: {validation_results['valid']} ({validation_results['valid']/validation_results['total']*100:.1f}%)\n"
|
| 523 |
+
report += f"Invalid: {validation_results['invalid']} ({validation_results['invalid']/validation_results['total']*100:.1f}%)\n\n"
|
| 524 |
+
|
| 525 |
+
if validation_results['invalid'] > 0:
|
| 526 |
+
report += "-" * 60 + "\n"
|
| 527 |
+
report += "QUALITY ISSUES FOUND:\n"
|
| 528 |
+
report += "-" * 60 + "\n"
|
| 529 |
+
|
| 530 |
+
# Count issue types
|
| 531 |
+
from collections import Counter
|
| 532 |
+
issue_counter = Counter(validation_results['issues'])
|
| 533 |
+
for issue, count in issue_counter.most_common():
|
| 534 |
+
report += f" - {issue}: {count} occurrences\n"
|
| 535 |
+
|
| 536 |
+
report += "\n"
|
| 537 |
+
|
| 538 |
+
# Add quality scores
|
| 539 |
+
report += "\n📊 QUALITY SCORE DISTRIBUTION:\n"
|
| 540 |
+
report += "-" * 60 + "\n"
|
| 541 |
+
|
| 542 |
+
scores = [validator.get_quality_score(ex) for ex in data]
|
| 543 |
+
avg_score = sum(scores) / len(scores)
|
| 544 |
+
min_score = min(scores)
|
| 545 |
+
max_score = max(scores)
|
| 546 |
+
|
| 547 |
+
report += f"Average Score: {avg_score:.1f}/100\n"
|
| 548 |
+
report += f"Range: {min_score:.0f} - {max_score:.0f}\n\n"
|
| 549 |
+
|
| 550 |
+
# Score distribution
|
| 551 |
+
excellent = sum(1 for s in scores if s >= 90)
|
| 552 |
+
good = sum(1 for s in scores if 75 <= s < 90)
|
| 553 |
+
fair = sum(1 for s in scores if 60 <= s < 75)
|
| 554 |
+
poor = sum(1 for s in scores if s < 60)
|
| 555 |
+
|
| 556 |
+
report += f"Excellent (90+): {excellent}\n"
|
| 557 |
+
report += f"Good (75-89): {good}\n"
|
| 558 |
+
report += f"Fair (60-74): {fair}\n"
|
| 559 |
+
report += f"Poor (<60): {poor}\n\n"
|
| 560 |
+
|
| 561 |
+
# Final status
|
| 562 |
+
report += "=" * 60 + "\n"
|
| 563 |
+
if validation_results['valid'] == validation_results['total'] and num_duplicates == 0:
|
| 564 |
+
report += "✅ PERFECT! All data is unique and high quality!\n"
|
| 565 |
+
elif validation_results['valid'] == validation_results['total']:
|
| 566 |
+
report += f"✅ GOOD! All unique data passed quality checks!\n"
|
| 567 |
+
elif validation_results['valid'] / validation_results['total'] >= 0.9:
|
| 568 |
+
report += "✅ GOOD QUALITY (90%+ valid)\n"
|
| 569 |
+
elif validation_results['valid'] / validation_results['total'] >= 0.7:
|
| 570 |
+
report += "⚠️ ACCEPTABLE QUALITY (70-90% valid)\n"
|
| 571 |
+
else:
|
| 572 |
+
report += "❌ POOR QUALITY (<70% valid)\n"
|
| 573 |
+
report += "=" * 60 + "\n"
|
| 574 |
+
|
| 575 |
+
return report
|
| 576 |
+
|
| 577 |
+
except Exception as e:
|
| 578 |
+
return f"❌ Error validating data: {e}"
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def remove_duplicates_from_data():
|
| 582 |
+
"""Remove duplicate questions from training data"""
|
| 583 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 584 |
+
|
| 585 |
+
if not Path(data_path).exists():
|
| 586 |
+
return "❌ No training data found!", 0, "", ""
|
| 587 |
+
|
| 588 |
+
try:
|
| 589 |
+
# Load data
|
| 590 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 591 |
+
data = json.load(f)
|
| 592 |
+
|
| 593 |
+
original_count = len(data)
|
| 594 |
+
|
| 595 |
+
# Remove duplicates
|
| 596 |
+
validator = QualityValidator()
|
| 597 |
+
deduplicated_data, num_removed = validator.remove_duplicates(data)
|
| 598 |
+
|
| 599 |
+
if num_removed == 0:
|
| 600 |
+
return (
|
| 601 |
+
"✅ No duplicates found! Your data is already clean.",
|
| 602 |
+
len(deduplicated_data),
|
| 603 |
+
json.dumps(deduplicated_data, indent=2),
|
| 604 |
+
f"✅ **Ready for Training**: {len(deduplicated_data)} unique examples!\n\n**Next Step**: Go to **⚙️ Training** tab"
|
| 605 |
+
)
|
| 606 |
+
|
| 607 |
+
# Save deduplicated data
|
| 608 |
+
with open(data_path, 'w', encoding='utf-8') as f:
|
| 609 |
+
json.dump(deduplicated_data, f, indent=2, ensure_ascii=False)
|
| 610 |
+
|
| 611 |
+
# Create backup of original
|
| 612 |
+
backup_path = data_path.replace('.json', '_with_duplicates_backup.json')
|
| 613 |
+
with open(backup_path, 'w', encoding='utf-8') as f:
|
| 614 |
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
| 615 |
+
|
| 616 |
+
message = f"""✅ **Deduplication Complete!**
|
| 617 |
+
|
| 618 |
+
**Removed:** {num_removed} duplicate questions
|
| 619 |
+
**Kept:** {len(deduplicated_data)} unique examples
|
| 620 |
+
**Original:** {original_count} total examples
|
| 621 |
+
|
| 622 |
+
**Backup saved to:** `{backup_path}`
|
| 623 |
+
|
| 624 |
+
Your training data now contains only unique questions. This will improve model quality and prevent overfitting.
|
| 625 |
+
"""
|
| 626 |
+
|
| 627 |
+
# Prepare preview
|
| 628 |
+
preview = f"📊 **Total Q&A Pairs:** {len(deduplicated_data)}\n\n"
|
| 629 |
+
preview += "**Sample Questions:**\n"
|
| 630 |
+
for i, item in enumerate(deduplicated_data[:3]):
|
| 631 |
+
preview += f"\n{i+1}. {item['instruction']}\n"
|
| 632 |
+
|
| 633 |
+
status = f"✅ **Ready for Training**: {len(deduplicated_data)} unique examples loaded!\n\n**Next Step**: Go to **⚙️ Training** tab"
|
| 634 |
+
|
| 635 |
+
return message, len(deduplicated_data), json.dumps(deduplicated_data, indent=2), status
|
| 636 |
+
|
| 637 |
+
except Exception as e:
|
| 638 |
+
return f"❌ Error removing duplicates: {str(e)}", 0, "", ""
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
# Model Selection Functions
|
| 642 |
+
def get_model_info(model_name):
|
| 643 |
+
"""Get detailed model information"""
|
| 644 |
+
global selected_model_id
|
| 645 |
+
|
| 646 |
+
# Get model ID from name
|
| 647 |
+
model_id = model_registry.get_model_id_from_name(model_name)
|
| 648 |
+
if not model_id:
|
| 649 |
+
return "❌ Model not found"
|
| 650 |
+
|
| 651 |
+
selected_model_id = model_id
|
| 652 |
+
model = model_registry.get_model(model_id)
|
| 653 |
+
|
| 654 |
+
info = f"# {model.name}\n\n"
|
| 655 |
+
info += f"**Type:** {model.type.upper()}\n"
|
| 656 |
+
info += f"**Path:** `{model.path}`\n"
|
| 657 |
+
info += f"**Size:** {model.size}\n"
|
| 658 |
+
info += f"**VRAM Required:** {model.vram_required}\n"
|
| 659 |
+
info += f"**Context Length:** {model.context_length:,} tokens\n"
|
| 660 |
+
info += f"**Recommended Quantization:** {model.quantization}\n"
|
| 661 |
+
info += f"**Recommended LoRA Rank:** {model.lora_rank}\n\n"
|
| 662 |
+
info += f"**Description:**\n{model.description}\n\n"
|
| 663 |
+
info += f"**Tags:** {', '.join(model.tags)}\n\n"
|
| 664 |
+
|
| 665 |
+
# Validate availability
|
| 666 |
+
is_valid, message = model_registry.validate_model_selection(model_id)
|
| 667 |
+
info += f"\n**Status:** {message}\n"
|
| 668 |
+
|
| 669 |
+
return info
|
| 670 |
+
|
| 671 |
+
|
| 672 |
+
def check_ollama_status():
|
| 673 |
+
"""Check Ollama status and list installed models"""
|
| 674 |
+
client = OllamaClient()
|
| 675 |
+
|
| 676 |
+
status = "# Ollama Status\n\n"
|
| 677 |
+
|
| 678 |
+
if client.is_available():
|
| 679 |
+
status += "✅ **Ollama is running**\n\n"
|
| 680 |
+
|
| 681 |
+
models = client.list_models()
|
| 682 |
+
if models:
|
| 683 |
+
status += f"**Installed Models ({len(models)}):**\n\n"
|
| 684 |
+
for model in models:
|
| 685 |
+
name = model.get("name", "unknown")
|
| 686 |
+
size = model.get("size", 0) / (1024**3) # Convert to GB
|
| 687 |
+
status += f"- `{name}` ({size:.1f}GB)\n"
|
| 688 |
+
else:
|
| 689 |
+
status += "⚠️ No models installed\n\n"
|
| 690 |
+
status += "Install models with: `ollama pull <model>`\n"
|
| 691 |
+
else:
|
| 692 |
+
status += "❌ **Ollama is not running**\n\n"
|
| 693 |
+
status += "Start Ollama with:\n"
|
| 694 |
+
status += "```bash\n"
|
| 695 |
+
status += "ollama serve\n"
|
| 696 |
+
status += "```\n\n"
|
| 697 |
+
status += "Or download from: https://ollama.com\n"
|
| 698 |
+
|
| 699 |
+
return status
|
| 700 |
+
|
| 701 |
+
|
| 702 |
+
def start_cloud_training(lora_rank, learning_rate, num_epochs, batch_size, grad_accum):
|
| 703 |
+
"""Start cloud training on RunPod"""
|
| 704 |
+
global training_status, runpod_manager, selected_model_id
|
| 705 |
+
|
| 706 |
+
if training_status["is_training"]:
|
| 707 |
+
return "⚠️ Training already in progress!", ""
|
| 708 |
+
|
| 709 |
+
# Check RunPod API key
|
| 710 |
+
runpod_key = secure_config.get_key("RUNPOD_API_KEY")
|
| 711 |
+
if not runpod_key:
|
| 712 |
+
return "❌ RunPod API key required for cloud training! Add it in Settings tab.", "❌ Missing RunPod API key"
|
| 713 |
+
|
| 714 |
+
# Check HF key
|
| 715 |
+
keys_ok, keys_msg = check_required_keys()
|
| 716 |
+
if not keys_ok:
|
| 717 |
+
return keys_msg, "❌ Missing API keys"
|
| 718 |
+
|
| 719 |
+
try:
|
| 720 |
+
training_status["is_training"] = True
|
| 721 |
+
log = "☁️ **CLOUD TRAINING ON RUNPOD**\n\n"
|
| 722 |
+
|
| 723 |
+
# Get selected model
|
| 724 |
+
model = model_registry.get_model(selected_model_id)
|
| 725 |
+
if not model:
|
| 726 |
+
training_status["is_training"] = False
|
| 727 |
+
return "❌ No model selected!", ""
|
| 728 |
+
|
| 729 |
+
log += f"📋 Configuration:\n"
|
| 730 |
+
log += f"- Model: {model.name}\n"
|
| 731 |
+
log += f"- LoRA Rank: {lora_rank}\n"
|
| 732 |
+
log += f"- Epochs: {num_epochs}\n"
|
| 733 |
+
log += f"- Mode: Cloud (RunPod)\n\n"
|
| 734 |
+
|
| 735 |
+
# Load training data
|
| 736 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 737 |
+
if not Path(data_path).exists():
|
| 738 |
+
training_status["is_training"] = False
|
| 739 |
+
return "❌ No training data found!", ""
|
| 740 |
+
|
| 741 |
+
with open(data_path, 'r', encoding='utf-8') as f:
|
| 742 |
+
data = json.load(f)
|
| 743 |
+
|
| 744 |
+
log += f"✅ Loaded {len(data)} training examples\n\n"
|
| 745 |
+
|
| 746 |
+
# Initialize RunPod manager
|
| 747 |
+
if not runpod_manager:
|
| 748 |
+
runpod_manager = RunPodManager(runpod_key)
|
| 749 |
+
|
| 750 |
+
log += "🚀 **STEP 1: Creating RunPod GPU Instance**\n"
|
| 751 |
+
log += "⏳ Finding available GPU (RTX 4090 recommended)...\n\n"
|
| 752 |
+
|
| 753 |
+
# Create pod config
|
| 754 |
+
from runpod_manager import DeploymentConfig
|
| 755 |
+
config = DeploymentConfig(
|
| 756 |
+
pod_name=f"aura-training-{datetime.now().strftime('%Y%m%d-%H%M')}",
|
| 757 |
+
gpu_type="NVIDIA GeForce RTX 4090",
|
| 758 |
+
storage_gb=50,
|
| 759 |
+
sync_data=True,
|
| 760 |
+
auto_setup=True
|
| 761 |
+
)
|
| 762 |
+
|
| 763 |
+
# Deploy pod
|
| 764 |
+
pod_info = runpod_manager.one_click_deploy(config=config)
|
| 765 |
+
|
| 766 |
+
log += f"✅ Pod created: {pod_info.id}\n"
|
| 767 |
+
log += f"📍 GPU: {pod_info.gpu_type}\n"
|
| 768 |
+
log += f"💰 Cost: ${pod_info.cost_per_hr:.2f}/hour\n\n"
|
| 769 |
+
|
| 770 |
+
log += "🚀 **STEP 2: Setting Up Training Environment**\n"
|
| 771 |
+
log += "⏳ Installing dependencies on cloud GPU...\n\n"
|
| 772 |
+
|
| 773 |
+
# Environment is auto-setup by one_click_deploy
|
| 774 |
+
|
| 775 |
+
log += "✅ Environment ready\n\n"
|
| 776 |
+
|
| 777 |
+
log += "🚀 **STEP 3: Uploading Training Data**\n"
|
| 778 |
+
log += f"⏳ Uploading {len(data)} examples to pod...\n\n"
|
| 779 |
+
|
| 780 |
+
# Data already synced by one_click_deploy if sync_data=True
|
| 781 |
+
|
| 782 |
+
log += "✅ Data uploaded\n\n"
|
| 783 |
+
|
| 784 |
+
log += "🚀 **STEP 4: Starting Training Job**\n"
|
| 785 |
+
log += f"⏳ Training {model.name} with LoRA...\n"
|
| 786 |
+
log += f"⏱️ Estimated time: {num_epochs * 30}-{num_epochs * 60} minutes\n\n"
|
| 787 |
+
|
| 788 |
+
# Create training config
|
| 789 |
+
from runpod_manager import TrainingConfig
|
| 790 |
+
train_config = TrainingConfig(
|
| 791 |
+
model_name=model.path if model.type != "ollama" else None,
|
| 792 |
+
dataset_path="/workspace/data/sample_financial_advisor_data.json",
|
| 793 |
+
output_dir="/workspace/models/financial_advisor",
|
| 794 |
+
lora_rank=int(lora_rank),
|
| 795 |
+
learning_rate=float(learning_rate),
|
| 796 |
+
num_epochs=int(num_epochs),
|
| 797 |
+
batch_size=int(batch_size),
|
| 798 |
+
gradient_accumulation_steps=int(grad_accum)
|
| 799 |
+
)
|
| 800 |
+
|
| 801 |
+
# Submit training job
|
| 802 |
+
job_result = runpod_manager.submit_training_job(pod_info.id, train_config)
|
| 803 |
+
|
| 804 |
+
log += "✅ Training started!\n\n"
|
| 805 |
+
log += "📊 **MONITORING TRAINING**\n"
|
| 806 |
+
log += "⏳ Training in progress... (this will take a while)\n\n"
|
| 807 |
+
|
| 808 |
+
# Note: In real implementation, we'd poll for completion
|
| 809 |
+
# For now, return success and let user check manually
|
| 810 |
+
|
| 811 |
+
log += f"🔗 **POD ACCESS**\n"
|
| 812 |
+
log += f"SSH: ssh root@{pod_info.ip} -p {pod_info.ssh_port}\n"
|
| 813 |
+
log += f"GUI: https://{pod_info.id}-7860.proxy.runpod.net\n\n"
|
| 814 |
+
|
| 815 |
+
log += "⚠️ **IMPORTANT:**\n"
|
| 816 |
+
log += "- Training is running on cloud GPU\n"
|
| 817 |
+
log += f"- Costing ${pod_info.cost_per_hr:.2f}/hour\n"
|
| 818 |
+
log += "- Go to RunPod tab to monitor or terminate\n"
|
| 819 |
+
log += "- Model will be saved to pod storage\n"
|
| 820 |
+
|
| 821 |
+
training_status["is_training"] = False
|
| 822 |
+
return log, "✅ Cloud training started!"
|
| 823 |
+
|
| 824 |
+
except Exception as e:
|
| 825 |
+
training_status["is_training"] = False
|
| 826 |
+
import traceback
|
| 827 |
+
error_details = traceback.format_exc()
|
| 828 |
+
return f"❌ Error: {str(e)}\n\n{error_details}", f"❌ Error: {e}"
|
| 829 |
+
|
| 830 |
+
|
| 831 |
+
def start_training(lora_rank, learning_rate, num_epochs, batch_size, grad_accum, training_mode):
|
| 832 |
+
"""Start the training process (local or cloud)"""
|
| 833 |
+
global training_status, trainer_instance, selected_model_id
|
| 834 |
+
|
| 835 |
+
# Route to cloud or local training
|
| 836 |
+
if training_mode == "Cloud GPU (RunPod)":
|
| 837 |
+
return start_cloud_training(lora_rank, learning_rate, num_epochs, batch_size, grad_accum)
|
| 838 |
+
|
| 839 |
+
# Local training below
|
| 840 |
+
if training_status["is_training"]:
|
| 841 |
+
return "⚠️ Training already in progress!", ""
|
| 842 |
+
|
| 843 |
+
# Check API keys first
|
| 844 |
+
keys_ok, keys_msg = check_required_keys()
|
| 845 |
+
if not keys_ok:
|
| 846 |
+
return keys_msg, "❌ Missing API keys. Go to Settings tab."
|
| 847 |
+
|
| 848 |
+
# Get selected model
|
| 849 |
+
model = model_registry.get_model(selected_model_id)
|
| 850 |
+
if not model:
|
| 851 |
+
return "❌ No model selected!", ""
|
| 852 |
+
|
| 853 |
+
# Validate model
|
| 854 |
+
is_valid, message = model_registry.validate_model_selection(selected_model_id)
|
| 855 |
+
if not is_valid:
|
| 856 |
+
return f"❌ Model validation failed: {message}", ""
|
| 857 |
+
|
| 858 |
+
# Get model path (convert Ollama to HF if needed)
|
| 859 |
+
if model.type == "ollama":
|
| 860 |
+
from ollama_integration import get_hf_model_for_ollama
|
| 861 |
+
model_path = get_hf_model_for_ollama(model.path)
|
| 862 |
+
if not model_path:
|
| 863 |
+
return f"❌ Cannot train with Ollama model {model.path}. No HuggingFace equivalent found.", ""
|
| 864 |
+
log_model_name = f"{model.name} (using HF: {model_path})"
|
| 865 |
+
else:
|
| 866 |
+
model_path = model.path
|
| 867 |
+
log_model_name = model.name
|
| 868 |
+
|
| 869 |
+
try:
|
| 870 |
+
training_status["is_training"] = True
|
| 871 |
+
training_status["current_epoch"] = 0
|
| 872 |
+
training_status["total_epochs"] = num_epochs
|
| 873 |
+
training_status["logs"] = []
|
| 874 |
+
|
| 875 |
+
log = "🚀 **STARTING TRAINING**\n\n"
|
| 876 |
+
log += f"⚙️ Configuration:\n"
|
| 877 |
+
log += f"- Model: {log_model_name}\n"
|
| 878 |
+
log += f"- LoRA Rank: {lora_rank}\n"
|
| 879 |
+
log += f"- Learning Rate: {learning_rate}\n"
|
| 880 |
+
log += f"- Epochs: {num_epochs}\n"
|
| 881 |
+
log += f"- Batch Size: {batch_size}\n"
|
| 882 |
+
log += f"- Gradient Accumulation: {grad_accum}\n\n"
|
| 883 |
+
|
| 884 |
+
training_status["logs"].append(log)
|
| 885 |
+
|
| 886 |
+
# Load data
|
| 887 |
+
log += "📂 Loading training data...\n"
|
| 888 |
+
data_path = "data/sample_financial_advisor_data.json"
|
| 889 |
+
|
| 890 |
+
if not Path(data_path).exists():
|
| 891 |
+
training_status["is_training"] = False
|
| 892 |
+
return "❌ No training data found! Add some Q&A pairs first.", ""
|
| 893 |
+
|
| 894 |
+
collector = JSONDataCollector()
|
| 895 |
+
data = collector.collect(data_path)
|
| 896 |
+
log += f"✅ Loaded {len(data)} examples\n\n"
|
| 897 |
+
training_status["logs"].append(log)
|
| 898 |
+
|
| 899 |
+
# Split data
|
| 900 |
+
log += "📊 Splitting dataset...\n"
|
| 901 |
+
builder = DatasetBuilder()
|
| 902 |
+
train_data, val_data, test_data = builder.train_test_split(data)
|
| 903 |
+
log += f"✅ Train: {len(train_data)}, Val: {len(val_data)}, Test: {len(test_data)}\n\n"
|
| 904 |
+
training_status["logs"].append(log)
|
| 905 |
+
|
| 906 |
+
# Configure LoRA
|
| 907 |
+
log += "⚙️ Configuring LoRA...\n"
|
| 908 |
+
lora_config = LoRAConfig(
|
| 909 |
+
r=int(lora_rank),
|
| 910 |
+
lora_alpha=int(lora_rank * 2),
|
| 911 |
+
lora_dropout=0.05,
|
| 912 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
|
| 913 |
+
bias="none",
|
| 914 |
+
task_type="CAUSAL_LM"
|
| 915 |
+
)
|
| 916 |
+
log += f"✅ LoRA configured\n\n"
|
| 917 |
+
training_status["logs"].append(log)
|
| 918 |
+
|
| 919 |
+
# Initialize trainer
|
| 920 |
+
log += "🤖 Initializing trainer...\n"
|
| 921 |
+
trainer_instance = LoRATrainer(
|
| 922 |
+
model_name=model_path,
|
| 923 |
+
lora_config=lora_config,
|
| 924 |
+
output_dir="models/financial_advisor"
|
| 925 |
+
)
|
| 926 |
+
log += "✅ Trainer ready\n\n"
|
| 927 |
+
training_status["logs"].append(log)
|
| 928 |
+
|
| 929 |
+
# Load model
|
| 930 |
+
log += f"📥 Loading {model.name} (this will take a few minutes)...\n"
|
| 931 |
+
log += f"⚠️ First time will download {model.size}\n\n"
|
| 932 |
+
training_status["logs"].append(log)
|
| 933 |
+
|
| 934 |
+
trainer_instance.load_model(use_4bit=True)
|
| 935 |
+
|
| 936 |
+
log += "✅ Model loaded successfully!\n\n"
|
| 937 |
+
training_status["logs"].append(log)
|
| 938 |
+
|
| 939 |
+
# Show parameters
|
| 940 |
+
params = trainer_instance.get_trainable_parameters()
|
| 941 |
+
log += f"📊 **Parameter Efficiency:**\n"
|
| 942 |
+
log += f"- Total: {params['total']:,}\n"
|
| 943 |
+
log += f"- Trainable: {params['trainable']:,}\n"
|
| 944 |
+
log += f"- Percentage: {params['percentage']:.2f}%\n\n"
|
| 945 |
+
training_status["logs"].append(log)
|
| 946 |
+
|
| 947 |
+
# Prepare datasets
|
| 948 |
+
log += "🔄 Preparing datasets...\n"
|
| 949 |
+
train_dataset, val_dataset = trainer_instance.prepare_dataset(train_data, val_data)
|
| 950 |
+
log += f"✅ Datasets tokenized and ready\n\n"
|
| 951 |
+
training_status["logs"].append(log)
|
| 952 |
+
|
| 953 |
+
# Start training
|
| 954 |
+
log += "🎯 **TRAINING STARTED**\n\n"
|
| 955 |
+
log += f"This will take approximately {num_epochs * 30}-{num_epochs * 60} minutes\n"
|
| 956 |
+
log += "You can monitor progress in TensorBoard:\n"
|
| 957 |
+
log += "`tensorboard --logdir models/financial_advisor/logs`\n\n"
|
| 958 |
+
training_status["logs"].append(log)
|
| 959 |
+
|
| 960 |
+
# Train (this will take a while)
|
| 961 |
+
history = trainer_instance.train(
|
| 962 |
+
train_dataset=train_dataset,
|
| 963 |
+
val_dataset=val_dataset,
|
| 964 |
+
num_epochs=int(num_epochs),
|
| 965 |
+
batch_size=int(batch_size),
|
| 966 |
+
learning_rate=float(learning_rate),
|
| 967 |
+
gradient_accumulation_steps=int(grad_accum)
|
| 968 |
+
)
|
| 969 |
+
|
| 970 |
+
log += "\n✅ **TRAINING COMPLETE!**\n\n"
|
| 971 |
+
log += f"💾 Model saved to: models/financial_advisor/final_model\n"
|
| 972 |
+
log += f"📊 Logs saved to: models/financial_advisor/logs\n\n"
|
| 973 |
+
training_status["logs"].append(log)
|
| 974 |
+
|
| 975 |
+
# Evaluate on test set
|
| 976 |
+
if len(test_data) > 0:
|
| 977 |
+
log += "🔍 **EVALUATING ON TEST SET**\n\n"
|
| 978 |
+
log += f"Running evaluation on {len(test_data)} test examples...\n"
|
| 979 |
+
training_status["logs"].append(log)
|
| 980 |
+
|
| 981 |
+
try:
|
| 982 |
+
# Run evaluation (limit to 50 samples for speed)
|
| 983 |
+
eval_results = trainer_instance.evaluate_on_test_set(
|
| 984 |
+
test_data=test_data,
|
| 985 |
+
model_name=log_model_name,
|
| 986 |
+
dataset_info=f"Financial Advisor Training - {len(train_data)} train examples",
|
| 987 |
+
num_samples=min(50, len(test_data))
|
| 988 |
+
)
|
| 989 |
+
|
| 990 |
+
log += "\n✅ **EVALUATION COMPLETE**\n\n"
|
| 991 |
+
log += "**Performance Metrics:**\n"
|
| 992 |
+
metrics = eval_results['metrics']
|
| 993 |
+
log += f"- Average Response Length: {metrics['avg_response_length']:.1f} words\n"
|
| 994 |
+
log += f"- Average Generation Time: {metrics['avg_generation_time']:.2f}s\n"
|
| 995 |
+
log += f"- Throughput: {metrics['examples_per_second']:.2f} examples/sec\n\n"
|
| 996 |
+
|
| 997 |
+
log += "📊 Evaluation report saved to: models/financial_advisor/evaluation_results/\n"
|
| 998 |
+
training_status["logs"].append(log)
|
| 999 |
+
|
| 1000 |
+
except Exception as eval_error:
|
| 1001 |
+
log += f"\n⚠️ Evaluation error (training still succeeded): {eval_error}\n"
|
| 1002 |
+
training_status["logs"].append(log)
|
| 1003 |
+
|
| 1004 |
+
training_status["is_training"] = False
|
| 1005 |
+
return "\n".join(training_status["logs"]), "✅ Training & Evaluation Complete!"
|
| 1006 |
+
|
| 1007 |
+
except Exception as e:
|
| 1008 |
+
training_status["is_training"] = False
|
| 1009 |
+
error_msg = f"\n\n❌ **ERROR:** {str(e)}\n\n"
|
| 1010 |
+
error_msg += "**Common fixes:**\n"
|
| 1011 |
+
error_msg += "- Out of memory: Reduce batch_size or increase grad_accum\n"
|
| 1012 |
+
error_msg += "- CUDA error: Make sure you have a GPU with 18GB+ VRAM\n"
|
| 1013 |
+
error_msg += "- Import error: Run `pip install bitsandbytes`\n"
|
| 1014 |
+
training_status["logs"].append(error_msg)
|
| 1015 |
+
return "\n".join(training_status["logs"]), f"❌ Error: {e}"
|
| 1016 |
+
|
| 1017 |
+
|
| 1018 |
+
def test_financial_advisor(question):
|
| 1019 |
+
"""Test the trained model"""
|
| 1020 |
+
global trainer_instance
|
| 1021 |
+
|
| 1022 |
+
if trainer_instance is None:
|
| 1023 |
+
return "❌ No model loaded. Train a model first or load an existing one."
|
| 1024 |
+
|
| 1025 |
+
try:
|
| 1026 |
+
# Format as financial advisor prompt
|
| 1027 |
+
system_prompt = (
|
| 1028 |
+
"You are an expert financial advisor with deep knowledge of personal finance, "
|
| 1029 |
+
"investments, retirement planning, tax strategies, and wealth management."
|
| 1030 |
+
)
|
| 1031 |
+
|
| 1032 |
+
prompt = f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
|
| 1033 |
+
prompt += f"<|im_start|>user\n{question}<|im_end|>\n"
|
| 1034 |
+
prompt += f"<|im_start|>assistant\n"
|
| 1035 |
+
|
| 1036 |
+
# Tokenize
|
| 1037 |
+
inputs = trainer_instance.tokenizer(prompt, return_tensors="pt").to(trainer_instance.peft_model.device)
|
| 1038 |
+
|
| 1039 |
+
# Generate
|
| 1040 |
+
outputs = trainer_instance.peft_model.generate(
|
| 1041 |
+
**inputs,
|
| 1042 |
+
max_new_tokens=512,
|
| 1043 |
+
temperature=0.7,
|
| 1044 |
+
top_p=0.9,
|
| 1045 |
+
do_sample=True,
|
| 1046 |
+
pad_token_id=trainer_instance.tokenizer.eos_token_id
|
| 1047 |
+
)
|
| 1048 |
+
|
| 1049 |
+
# Decode
|
| 1050 |
+
response = trainer_instance.tokenizer.decode(outputs[0], skip_special_tokens=False)
|
| 1051 |
+
|
| 1052 |
+
# Extract assistant response
|
| 1053 |
+
if "<|im_start|>assistant" in response:
|
| 1054 |
+
response = response.split("<|im_start|>assistant")[-1].strip()
|
| 1055 |
+
response = response.replace("<|im_end|>", "").strip()
|
| 1056 |
+
|
| 1057 |
+
return response
|
| 1058 |
+
|
| 1059 |
+
except Exception as e:
|
| 1060 |
+
return f"❌ Error generating response: {e}"
|
| 1061 |
+
|
| 1062 |
+
|
| 1063 |
+
def load_existing_model():
|
| 1064 |
+
"""Load a previously trained model"""
|
| 1065 |
+
global trainer_instance, selected_model_id
|
| 1066 |
+
|
| 1067 |
+
model_path = "models/financial_advisor/final_model"
|
| 1068 |
+
|
| 1069 |
+
if not Path(model_path).exists():
|
| 1070 |
+
return "❌ No trained model found at models/financial_advisor/final_model"
|
| 1071 |
+
|
| 1072 |
+
try:
|
| 1073 |
+
# Get the model that was used for training
|
| 1074 |
+
model = model_registry.get_model(selected_model_id)
|
| 1075 |
+
if not model:
|
| 1076 |
+
return "❌ Model not found in registry"
|
| 1077 |
+
|
| 1078 |
+
# Get actual model path
|
| 1079 |
+
if model.type == "ollama":
|
| 1080 |
+
from ollama_integration import get_hf_model_for_ollama
|
| 1081 |
+
base_model_path = get_hf_model_for_ollama(model.path)
|
| 1082 |
+
if not base_model_path:
|
| 1083 |
+
return f"❌ Cannot load Ollama model {model.path}"
|
| 1084 |
+
else:
|
| 1085 |
+
base_model_path = model.path
|
| 1086 |
+
|
| 1087 |
+
# Initialize trainer
|
| 1088 |
+
lora_config = LoRAConfig(r=model.lora_rank, lora_alpha=model.lora_rank*2)
|
| 1089 |
+
trainer_instance = LoRATrainer(
|
| 1090 |
+
model_name=base_model_path,
|
| 1091 |
+
lora_config=lora_config,
|
| 1092 |
+
output_dir="models/financial_advisor"
|
| 1093 |
+
)
|
| 1094 |
+
|
| 1095 |
+
# Load base model
|
| 1096 |
+
trainer_instance.load_model(use_4bit=True)
|
| 1097 |
+
|
| 1098 |
+
# Load adapter
|
| 1099 |
+
trainer_instance.load_adapter(model_path)
|
| 1100 |
+
|
| 1101 |
+
return f"✅ Model loaded successfully! ({model.name}) You can now test it."
|
| 1102 |
+
|
| 1103 |
+
except Exception as e:
|
| 1104 |
+
return f"❌ Error loading model: {e}"
|
| 1105 |
+
|
| 1106 |
+
|
| 1107 |
+
# Model Comparison and Evaluation Functions
|
| 1108 |
+
def load_evaluation_history():
|
| 1109 |
+
"""Load evaluation history and display summary"""
|
| 1110 |
+
try:
|
| 1111 |
+
from evaluation.model_evaluator import ModelEvaluator
|
| 1112 |
+
|
| 1113 |
+
evaluator = ModelEvaluator(output_dir="models/financial_advisor/evaluation_results")
|
| 1114 |
+
|
| 1115 |
+
if not evaluator.history:
|
| 1116 |
+
return "❌ No evaluation history found. Train a model first to see evaluations."
|
| 1117 |
+
|
| 1118 |
+
# Generate summary
|
| 1119 |
+
summary = f"📊 **EVALUATION HISTORY**\n\n"
|
| 1120 |
+
summary += f"Total Evaluations: {len(evaluator.history)}\n\n"
|
| 1121 |
+
summary += "=" * 70 + "\n\n"
|
| 1122 |
+
|
| 1123 |
+
for i, eval_result in enumerate(reversed(evaluator.history[-10:]), 1):
|
| 1124 |
+
summary += f"**Evaluation {len(evaluator.history) - i + 1}:**\n"
|
| 1125 |
+
summary += f"- Model: {eval_result['model_name']}\n"
|
| 1126 |
+
summary += f"- Dataset: {eval_result['dataset_info']}\n"
|
| 1127 |
+
summary += f"- Timestamp: {eval_result['timestamp']}\n"
|
| 1128 |
+
summary += f"- Test Examples: {eval_result['num_test_examples']}\n"
|
| 1129 |
+
|
| 1130 |
+
metrics = eval_result.get('metrics', {})
|
| 1131 |
+
summary += f"- Avg Response Length: {metrics.get('avg_response_length', 0):.1f} words\n"
|
| 1132 |
+
summary += f"- Avg Generation Time: {metrics.get('avg_generation_time', 0):.2f}s\n"
|
| 1133 |
+
summary += f"- Throughput: {metrics.get('examples_per_second', 0):.2f} ex/s\n"
|
| 1134 |
+
summary += "\n" + "-" * 70 + "\n\n"
|
| 1135 |
+
|
| 1136 |
+
return summary
|
| 1137 |
+
|
| 1138 |
+
except Exception as e:
|
| 1139 |
+
return f"❌ Error loading evaluation history: {e}"
|
| 1140 |
+
|
| 1141 |
+
|
| 1142 |
+
def compare_models(num_models):
|
| 1143 |
+
"""Compare recent model evaluations"""
|
| 1144 |
+
try:
|
| 1145 |
+
from evaluation.model_evaluator import ModelEvaluator
|
| 1146 |
+
|
| 1147 |
+
evaluator = ModelEvaluator(output_dir="models/financial_advisor/evaluation_results")
|
| 1148 |
+
|
| 1149 |
+
if not evaluator.history:
|
| 1150 |
+
return "❌ No evaluation history found. Train models first to compare."
|
| 1151 |
+
|
| 1152 |
+
# Get comparison
|
| 1153 |
+
comparison = evaluator.compare_models(num_recent=int(num_models))
|
| 1154 |
+
|
| 1155 |
+
# Generate report
|
| 1156 |
+
report = evaluator.generate_comparison_report(comparison)
|
| 1157 |
+
|
| 1158 |
+
return report
|
| 1159 |
+
|
| 1160 |
+
except Exception as e:
|
| 1161 |
+
return f"❌ Error comparing models: {e}"
|
| 1162 |
+
|
| 1163 |
+
|
| 1164 |
+
def view_latest_evaluation():
|
| 1165 |
+
"""View the most recent evaluation in detail"""
|
| 1166 |
+
try:
|
| 1167 |
+
from evaluation.model_evaluator import ModelEvaluator
|
| 1168 |
+
|
| 1169 |
+
evaluator = ModelEvaluator(output_dir="models/financial_advisor/evaluation_results")
|
| 1170 |
+
|
| 1171 |
+
latest = evaluator.get_latest_evaluation()
|
| 1172 |
+
if not latest:
|
| 1173 |
+
return "❌ No evaluations found. Train a model first."
|
| 1174 |
+
|
| 1175 |
+
# Generate detailed report
|
| 1176 |
+
report = evaluator.generate_report(latest)
|
| 1177 |
+
|
| 1178 |
+
return report
|
| 1179 |
+
|
| 1180 |
+
except Exception as e:
|
| 1181 |
+
return f"❌ Error viewing evaluation: {e}"
|
| 1182 |
+
|
| 1183 |
+
|
| 1184 |
+
# RunPod Functions
|
| 1185 |
+
def init_runpod_manager():
|
| 1186 |
+
"""Initialize RunPod manager with API key"""
|
| 1187 |
+
global runpod_manager
|
| 1188 |
+
|
| 1189 |
+
if runpod_manager is not None:
|
| 1190 |
+
return "✅ RunPod manager already initialized"
|
| 1191 |
+
|
| 1192 |
+
api_key = secure_config.get_key("RUNPOD_API_KEY")
|
| 1193 |
+
if not api_key:
|
| 1194 |
+
return "❌ RunPod API key not found! Please add it in the Settings tab."
|
| 1195 |
+
|
| 1196 |
+
try:
|
| 1197 |
+
runpod_manager = RunPodManager(api_key)
|
| 1198 |
+
return "✅ RunPod manager initialized successfully!"
|
| 1199 |
+
except Exception as e:
|
| 1200 |
+
return f"❌ Error initializing RunPod manager: {e}"
|
| 1201 |
+
|
| 1202 |
+
|
| 1203 |
+
def list_runpod_pods():
|
| 1204 |
+
"""List all RunPod pods"""
|
| 1205 |
+
global runpod_manager
|
| 1206 |
+
|
| 1207 |
+
if not runpod_manager:
|
| 1208 |
+
init_result = init_runpod_manager()
|
| 1209 |
+
if "❌" in init_result:
|
| 1210 |
+
return init_result
|
| 1211 |
+
|
| 1212 |
+
try:
|
| 1213 |
+
pods = runpod_manager.list_pods()
|
| 1214 |
+
|
| 1215 |
+
if not pods:
|
| 1216 |
+
return "No pods found. Create a new pod to get started!"
|
| 1217 |
+
|
| 1218 |
+
output = f"📊 **YOUR RUNPOD PODS** ({len(pods)} total)\n\n"
|
| 1219 |
+
|
| 1220 |
+
for pod in pods:
|
| 1221 |
+
output += f"**{pod.name}** (ID: {pod.id[:8]}...)\n"
|
| 1222 |
+
output += f" Status: {pod.status}\n"
|
| 1223 |
+
output += f" GPU: {pod.gpu_count}x {pod.gpu_type}\n"
|
| 1224 |
+
output += f" Cost: ${pod.cost_per_hr:.2f}/hr\n"
|
| 1225 |
+
|
| 1226 |
+
if pod.status == "RUNNING":
|
| 1227 |
+
if pod.ip and pod.ssh_port:
|
| 1228 |
+
output += f" SSH: {pod.ip}:{pod.ssh_port}\n"
|
| 1229 |
+
if pod.port:
|
| 1230 |
+
output += f" GUI: https://{pod.id}-7860.proxy.runpod.net\n"
|
| 1231 |
+
|
| 1232 |
+
output += "\n"
|
| 1233 |
+
|
| 1234 |
+
return output
|
| 1235 |
+
|
| 1236 |
+
except Exception as e:
|
| 1237 |
+
return f"❌ Error listing pods: {e}"
|
| 1238 |
+
|
| 1239 |
+
|
| 1240 |
+
def create_runpod_pod(pod_name, gpu_type, storage_gb, sync_data):
|
| 1241 |
+
"""Create a new RunPod pod"""
|
| 1242 |
+
global runpod_manager, current_pod_info, deployment_in_progress
|
| 1243 |
+
|
| 1244 |
+
if deployment_in_progress:
|
| 1245 |
+
return "⚠️ Deployment already in progress!", ""
|
| 1246 |
+
|
| 1247 |
+
if not runpod_manager:
|
| 1248 |
+
init_result = init_runpod_manager()
|
| 1249 |
+
if "❌" in init_result:
|
| 1250 |
+
return init_result, ""
|
| 1251 |
+
|
| 1252 |
+
deployment_in_progress = True
|
| 1253 |
+
log_output = []
|
| 1254 |
+
|
| 1255 |
+
def log_callback(msg):
|
| 1256 |
+
log_output.append(msg)
|
| 1257 |
+
|
| 1258 |
+
try:
|
| 1259 |
+
config = DeploymentConfig(
|
| 1260 |
+
pod_name=pod_name or "aura-training-pod",
|
| 1261 |
+
gpu_type=gpu_type,
|
| 1262 |
+
storage_gb=int(storage_gb),
|
| 1263 |
+
sync_data=sync_data,
|
| 1264 |
+
auto_setup=True
|
| 1265 |
+
)
|
| 1266 |
+
|
| 1267 |
+
pod_info = runpod_manager.one_click_deploy(
|
| 1268 |
+
config=config,
|
| 1269 |
+
progress_callback=log_callback
|
| 1270 |
+
)
|
| 1271 |
+
|
| 1272 |
+
current_pod_info = pod_info
|
| 1273 |
+
deployment_in_progress = False
|
| 1274 |
+
|
| 1275 |
+
final_log = "\n".join(log_output)
|
| 1276 |
+
status = f"✅ Pod created successfully!\n"
|
| 1277 |
+
status += f"ID: {pod_info.id}\n"
|
| 1278 |
+
status += f"SSH: {pod_info.ip}:{pod_info.ssh_port}\n"
|
| 1279 |
+
status += f"GUI: https://{pod_info.id}-7860.proxy.runpod.net"
|
| 1280 |
+
|
| 1281 |
+
return final_log, status
|
| 1282 |
+
|
| 1283 |
+
except Exception as e:
|
| 1284 |
+
deployment_in_progress = False
|
| 1285 |
+
error_log = "\n".join(log_output) + f"\n\n❌ Error: {e}"
|
| 1286 |
+
return error_log, f"❌ Deployment failed: {e}"
|
| 1287 |
+
|
| 1288 |
+
|
| 1289 |
+
def stop_runpod_pod(pod_id):
|
| 1290 |
+
"""Stop a running pod"""
|
| 1291 |
+
global runpod_manager
|
| 1292 |
+
|
| 1293 |
+
if not runpod_manager:
|
| 1294 |
+
init_result = init_runpod_manager()
|
| 1295 |
+
if "❌" in init_result:
|
| 1296 |
+
return init_result
|
| 1297 |
+
|
| 1298 |
+
try:
|
| 1299 |
+
success = runpod_manager.stop_pod(pod_id)
|
| 1300 |
+
if success:
|
| 1301 |
+
return f"✅ Pod {pod_id} stopped successfully!"
|
| 1302 |
+
else:
|
| 1303 |
+
return f"❌ Failed to stop pod {pod_id}"
|
| 1304 |
+
except Exception as e:
|
| 1305 |
+
return f"❌ Error stopping pod: {e}"
|
| 1306 |
+
|
| 1307 |
+
|
| 1308 |
+
def terminate_runpod_pod(pod_id):
|
| 1309 |
+
"""Terminate (delete) a pod"""
|
| 1310 |
+
global runpod_manager, current_pod_info
|
| 1311 |
+
|
| 1312 |
+
if not runpod_manager:
|
| 1313 |
+
init_result = init_runpod_manager()
|
| 1314 |
+
if "❌" in init_result:
|
| 1315 |
+
return init_result
|
| 1316 |
+
|
| 1317 |
+
try:
|
| 1318 |
+
success = runpod_manager.terminate_pod(pod_id)
|
| 1319 |
+
if success:
|
| 1320 |
+
if current_pod_info and current_pod_info.id == pod_id:
|
| 1321 |
+
current_pod_info = None
|
| 1322 |
+
return f"✅ Pod {pod_id} terminated successfully!"
|
| 1323 |
+
else:
|
| 1324 |
+
return f"❌ Failed to terminate pod {pod_id}"
|
| 1325 |
+
except Exception as e:
|
| 1326 |
+
return f"❌ Error terminating pod: {e}"
|
| 1327 |
+
|
| 1328 |
+
|
| 1329 |
+
def get_pod_connection_info(pod_id):
|
| 1330 |
+
"""Get connection info for a pod"""
|
| 1331 |
+
global runpod_manager
|
| 1332 |
+
|
| 1333 |
+
if not runpod_manager:
|
| 1334 |
+
init_result = init_runpod_manager()
|
| 1335 |
+
if "❌" in init_result:
|
| 1336 |
+
return init_result
|
| 1337 |
+
|
| 1338 |
+
try:
|
| 1339 |
+
pod = runpod_manager.get_pod(pod_id)
|
| 1340 |
+
if not pod:
|
| 1341 |
+
return f"❌ Pod {pod_id} not found"
|
| 1342 |
+
|
| 1343 |
+
info = f"# Connection Info for {pod.name}\n\n"
|
| 1344 |
+
info += f"**Status:** {pod.status}\n\n"
|
| 1345 |
+
|
| 1346 |
+
if pod.status == "RUNNING":
|
| 1347 |
+
info += "## SSH Connection\n"
|
| 1348 |
+
info += f"```bash\n"
|
| 1349 |
+
info += f"ssh root@{pod.ip} -p {pod.ssh_port}\n"
|
| 1350 |
+
info += f"```\n\n"
|
| 1351 |
+
|
| 1352 |
+
info += "## GUI Access\n"
|
| 1353 |
+
info += f"Open in browser:\n"
|
| 1354 |
+
info += f"```\n"
|
| 1355 |
+
info += f"https://{pod.id}-7860.proxy.runpod.net\n"
|
| 1356 |
+
info += f"```\n\n"
|
| 1357 |
+
|
| 1358 |
+
info += "## Details\n"
|
| 1359 |
+
info += f"- GPU: {pod.gpu_count}x {pod.gpu_type}\n"
|
| 1360 |
+
info += f"- Cost: ${pod.cost_per_hr:.2f}/hour\n"
|
| 1361 |
+
else:
|
| 1362 |
+
info += f"⚠️ Pod is not running (Status: {pod.status})\n"
|
| 1363 |
+
|
| 1364 |
+
return info
|
| 1365 |
+
|
| 1366 |
+
except Exception as e:
|
| 1367 |
+
return f"❌ Error getting pod info: {e}"
|
| 1368 |
+
|
| 1369 |
+
|
| 1370 |
+
# Create Gradio interface with custom CSS for scrolling
|
| 1371 |
+
custom_css = """
|
| 1372 |
+
/* Make all containers properly scrollable */
|
| 1373 |
+
.overflow-y-auto {
|
| 1374 |
+
overflow-y: auto !important;
|
| 1375 |
+
max-height: 600px !important;
|
| 1376 |
+
}
|
| 1377 |
+
|
| 1378 |
+
/* Textbox scrolling */
|
| 1379 |
+
.textbox-container textarea {
|
| 1380 |
+
max-height: 400px !important;
|
| 1381 |
+
overflow-y: auto !important;
|
| 1382 |
+
}
|
| 1383 |
+
|
| 1384 |
+
/* Column scrolling for long content */
|
| 1385 |
+
.gr-column {
|
| 1386 |
+
overflow-y: auto !important;
|
| 1387 |
+
max-height: 800px !important;
|
| 1388 |
+
}
|
| 1389 |
+
|
| 1390 |
+
/* Markdown blocks in columns */
|
| 1391 |
+
.gr-column .gr-markdown {
|
| 1392 |
+
overflow-y: auto !important;
|
| 1393 |
+
max-height: 500px !important;
|
| 1394 |
+
}
|
| 1395 |
+
|
| 1396 |
+
/* Accordion-like sections */
|
| 1397 |
+
.gr-box {
|
| 1398 |
+
overflow-y: auto !important;
|
| 1399 |
+
max-height: 600px !important;
|
| 1400 |
+
}
|
| 1401 |
+
|
| 1402 |
+
/* Hide footer */
|
| 1403 |
+
footer {visibility: hidden}
|
| 1404 |
+
"""
|
| 1405 |
+
|
| 1406 |
+
with gr.Blocks(title="Financial Advisor AI Trainer", theme=gr.themes.Soft(), css=custom_css) as demo:
|
| 1407 |
+
|
| 1408 |
+
gr.Markdown("""
|
| 1409 |
+
# 🏦 Financial Advisor AI Training Studio
|
| 1410 |
+
|
| 1411 |
+
Train your own expert financial advisor AI powered by Qwen 3 30B!
|
| 1412 |
+
No coding required - just configure, train, and test.
|
| 1413 |
+
""")
|
| 1414 |
+
|
| 1415 |
+
with gr.Tabs():
|
| 1416 |
+
|
| 1417 |
+
# Tab 0: Settings & API Keys
|
| 1418 |
+
with gr.Tab("⚙️ Settings"):
|
| 1419 |
+
gr.Markdown("""
|
| 1420 |
+
# API Key Management
|
| 1421 |
+
|
| 1422 |
+
Store your API keys securely. Keys are encrypted and never stored in plain text.
|
| 1423 |
+
**You only need to enter these once!**
|
| 1424 |
+
""")
|
| 1425 |
+
|
| 1426 |
+
with gr.Row():
|
| 1427 |
+
with gr.Column():
|
| 1428 |
+
gr.Markdown("### 🔑 API Keys")
|
| 1429 |
+
|
| 1430 |
+
gr.Markdown("#### HuggingFace Token (Required)")
|
| 1431 |
+
gr.Markdown("Get your token at: https://huggingface.co/settings/tokens")
|
| 1432 |
+
hf_token_input = gr.Textbox(
|
| 1433 |
+
label="HuggingFace Token",
|
| 1434 |
+
placeholder="hf_...",
|
| 1435 |
+
type="password",
|
| 1436 |
+
info="Required for downloading Qwen 3 model"
|
| 1437 |
+
)
|
| 1438 |
+
|
| 1439 |
+
gr.Markdown("---")
|
| 1440 |
+
gr.Markdown("#### Optional API Keys")
|
| 1441 |
+
|
| 1442 |
+
openai_key_input = gr.Textbox(
|
| 1443 |
+
label="OpenAI API Key (Optional)",
|
| 1444 |
+
placeholder="sk-...",
|
| 1445 |
+
type="password",
|
| 1446 |
+
info="For testing against GPT models"
|
| 1447 |
+
)
|
| 1448 |
+
|
| 1449 |
+
anthropic_key_input = gr.Textbox(
|
| 1450 |
+
label="Anthropic API Key (Optional)",
|
| 1451 |
+
placeholder="sk-ant-...",
|
| 1452 |
+
type="password",
|
| 1453 |
+
info="For testing against Claude models"
|
| 1454 |
+
)
|
| 1455 |
+
|
| 1456 |
+
wandb_key_input = gr.Textbox(
|
| 1457 |
+
label="Weights & Biases API Key (Optional)",
|
| 1458 |
+
placeholder="...",
|
| 1459 |
+
type="password",
|
| 1460 |
+
info="For advanced experiment tracking"
|
| 1461 |
+
)
|
| 1462 |
+
|
| 1463 |
+
runpod_key_input = gr.Textbox(
|
| 1464 |
+
label="RunPod API Key (Optional)",
|
| 1465 |
+
placeholder="...",
|
| 1466 |
+
type="password",
|
| 1467 |
+
info="For cloud GPU deployment"
|
| 1468 |
+
)
|
| 1469 |
+
|
| 1470 |
+
gr.Markdown("---")
|
| 1471 |
+
gr.Markdown("#### Custom API Keys (Advanced)")
|
| 1472 |
+
custom_keys_input = gr.Code(
|
| 1473 |
+
label="Custom Keys (JSON format)",
|
| 1474 |
+
language="json",
|
| 1475 |
+
value='{\n "MY_API_KEY": "value",\n "OTHER_KEY": "value"\n}',
|
| 1476 |
+
lines=5
|
| 1477 |
+
)
|
| 1478 |
+
|
| 1479 |
+
gr.Markdown("---")
|
| 1480 |
+
|
| 1481 |
+
with gr.Row():
|
| 1482 |
+
save_keys_btn = gr.Button("💾 Save All Keys", variant="primary", size="lg")
|
| 1483 |
+
load_keys_btn = gr.Button("🔄 Load Saved Keys", variant="secondary")
|
| 1484 |
+
clear_keys_btn = gr.Button("🗑️ Clear All Keys", variant="stop")
|
| 1485 |
+
|
| 1486 |
+
keys_status = gr.Markdown()
|
| 1487 |
+
|
| 1488 |
+
with gr.Column():
|
| 1489 |
+
gr.Markdown("### 📋 Security & Status")
|
| 1490 |
+
|
| 1491 |
+
gr.Markdown("""
|
| 1492 |
+
#### 🔒 Security Features
|
| 1493 |
+
- ✅ Keys are encrypted using Fernet encryption
|
| 1494 |
+
- ✅ Stored in `.secrets/` directory (auto-hidden)
|
| 1495 |
+
- ✅ Never logged or displayed in full
|
| 1496 |
+
- ✅ Loaded into environment variables automatically
|
| 1497 |
+
- ✅ Only you can decrypt with your machine
|
| 1498 |
+
|
| 1499 |
+
#### 📍 Storage Location
|
| 1500 |
+
Keys are saved in: `.secrets/config.enc`
|
| 1501 |
+
|
| 1502 |
+
⚠️ **Backup Note**: If you reinstall or move the project,
|
| 1503 |
+
you'll need to re-enter your keys.
|
| 1504 |
+
""")
|
| 1505 |
+
|
| 1506 |
+
gr.Markdown("---")
|
| 1507 |
+
gr.Markdown("### 🔍 Current Keys Status")
|
| 1508 |
+
|
| 1509 |
+
keys_display = gr.Markdown("No keys loaded. Click 'Load Saved Keys' to check.")
|
| 1510 |
+
|
| 1511 |
+
gr.Markdown("---")
|
| 1512 |
+
gr.Markdown("""
|
| 1513 |
+
### 💡 Tips
|
| 1514 |
+
|
| 1515 |
+
**HuggingFace Token:**
|
| 1516 |
+
1. Go to https://huggingface.co/settings/tokens
|
| 1517 |
+
2. Create a new token (read access is enough)
|
| 1518 |
+
3. Copy and paste it above
|
| 1519 |
+
4. Click "Save All Keys"
|
| 1520 |
+
|
| 1521 |
+
**Other Keys:**
|
| 1522 |
+
Only add if you plan to use those services.
|
| 1523 |
+
Training works with just HuggingFace token.
|
| 1524 |
+
""")
|
| 1525 |
+
|
| 1526 |
+
# Wire up settings
|
| 1527 |
+
save_keys_btn.click(
|
| 1528 |
+
fn=save_api_keys,
|
| 1529 |
+
inputs=[hf_token_input, openai_key_input, anthropic_key_input, wandb_key_input, runpod_key_input, custom_keys_input],
|
| 1530 |
+
outputs=keys_status
|
| 1531 |
+
)
|
| 1532 |
+
|
| 1533 |
+
load_keys_btn.click(
|
| 1534 |
+
fn=load_api_keys,
|
| 1535 |
+
outputs=[hf_token_input, openai_key_input, anthropic_key_input, wandb_key_input, runpod_key_input, custom_keys_input, keys_display]
|
| 1536 |
+
)
|
| 1537 |
+
|
| 1538 |
+
clear_keys_btn.click(
|
| 1539 |
+
fn=clear_api_keys,
|
| 1540 |
+
outputs=[keys_status, hf_token_input, openai_key_input, anthropic_key_input, wandb_key_input, runpod_key_input, custom_keys_input]
|
| 1541 |
+
)
|
| 1542 |
+
|
| 1543 |
+
# Load keys on startup
|
| 1544 |
+
demo.load(
|
| 1545 |
+
fn=load_api_keys,
|
| 1546 |
+
outputs=[hf_token_input, openai_key_input, anthropic_key_input, wandb_key_input, runpod_key_input, custom_keys_input, keys_display]
|
| 1547 |
+
)
|
| 1548 |
+
|
| 1549 |
+
# Tab 1: Data Management
|
| 1550 |
+
with gr.Tab("📚 Training Data"):
|
| 1551 |
+
gr.Markdown("""
|
| 1552 |
+
### Manage Your Training Data
|
| 1553 |
+
|
| 1554 |
+
**Your training data is automatically used when you click "Start Training" in the Training tab.**
|
| 1555 |
+
""")
|
| 1556 |
+
|
| 1557 |
+
with gr.Row():
|
| 1558 |
+
with gr.Column():
|
| 1559 |
+
gr.Markdown("#### Current Training Data")
|
| 1560 |
+
data_preview = gr.Markdown()
|
| 1561 |
+
data_count = gr.Number(label="Total Q&A Pairs", interactive=False)
|
| 1562 |
+
|
| 1563 |
+
gr.Markdown("---")
|
| 1564 |
+
|
| 1565 |
+
# Training readiness status
|
| 1566 |
+
training_ready_status = gr.Markdown("Click 'Refresh Data' to check training readiness")
|
| 1567 |
+
|
| 1568 |
+
with gr.Row():
|
| 1569 |
+
load_data_btn = gr.Button("🔄 Refresh Data", variant="secondary")
|
| 1570 |
+
validate_data_btn = gr.Button("🔍 Validate Quality", variant="secondary")
|
| 1571 |
+
remove_dupes_btn = gr.Button("🧹 Remove Duplicates", variant="secondary")
|
| 1572 |
+
|
| 1573 |
+
gr.Markdown("---")
|
| 1574 |
+
gr.Markdown("#### Quality Report")
|
| 1575 |
+
validation_report = gr.Textbox(
|
| 1576 |
+
label="Data Quality Analysis",
|
| 1577 |
+
lines=10,
|
| 1578 |
+
max_lines=15,
|
| 1579 |
+
interactive=False,
|
| 1580 |
+
placeholder="Click 'Validate Quality' to analyze your training data...",
|
| 1581 |
+
show_copy_button=True
|
| 1582 |
+
)
|
| 1583 |
+
|
| 1584 |
+
gr.Markdown("---")
|
| 1585 |
+
gr.Markdown("#### Deduplication Status")
|
| 1586 |
+
dedup_status = gr.Markdown("Click 'Remove Duplicates' to clean your data")
|
| 1587 |
+
|
| 1588 |
+
gr.Markdown("---")
|
| 1589 |
+
gr.Markdown("#### View Full Dataset")
|
| 1590 |
+
data_json = gr.Code(label="Full Dataset (JSON)", language="json", lines=8, max_lines=12)
|
| 1591 |
+
|
| 1592 |
+
with gr.Column():
|
| 1593 |
+
gr.Markdown("#### Add New Training Example")
|
| 1594 |
+
new_question = gr.Textbox(
|
| 1595 |
+
label="Question",
|
| 1596 |
+
placeholder="What is the difference between a Roth IRA and Traditional IRA?",
|
| 1597 |
+
lines=3
|
| 1598 |
+
)
|
| 1599 |
+
new_context = gr.Textbox(
|
| 1600 |
+
label="Context (optional)",
|
| 1601 |
+
placeholder="Additional context for the question...",
|
| 1602 |
+
lines=2
|
| 1603 |
+
)
|
| 1604 |
+
new_answer = gr.Textbox(
|
| 1605 |
+
label="Expert Answer",
|
| 1606 |
+
placeholder="Provide a detailed, accurate financial answer...",
|
| 1607 |
+
lines=8
|
| 1608 |
+
)
|
| 1609 |
+
add_example_btn = gr.Button("➕ Add Example", variant="primary")
|
| 1610 |
+
add_status = gr.Markdown()
|
| 1611 |
+
|
| 1612 |
+
# Wire up data management
|
| 1613 |
+
load_data_btn.click(
|
| 1614 |
+
fn=load_training_data,
|
| 1615 |
+
outputs=[data_preview, data_count, data_json, training_ready_status]
|
| 1616 |
+
)
|
| 1617 |
+
|
| 1618 |
+
validate_data_btn.click(
|
| 1619 |
+
fn=validate_training_data,
|
| 1620 |
+
outputs=validation_report
|
| 1621 |
+
).then(
|
| 1622 |
+
fn=load_training_data,
|
| 1623 |
+
outputs=[data_preview, data_count, data_json, training_ready_status]
|
| 1624 |
+
)
|
| 1625 |
+
|
| 1626 |
+
remove_dupes_btn.click(
|
| 1627 |
+
fn=remove_duplicates_from_data,
|
| 1628 |
+
outputs=[dedup_status, data_count, data_json, training_ready_status]
|
| 1629 |
+
).then(
|
| 1630 |
+
fn=load_training_data,
|
| 1631 |
+
outputs=[data_preview, data_count, data_json, training_ready_status]
|
| 1632 |
+
)
|
| 1633 |
+
|
| 1634 |
+
add_example_btn.click(
|
| 1635 |
+
fn=add_training_example,
|
| 1636 |
+
inputs=[new_question, new_answer, new_context],
|
| 1637 |
+
outputs=[add_status, data_count]
|
| 1638 |
+
).then(
|
| 1639 |
+
fn=load_training_data,
|
| 1640 |
+
outputs=[data_preview, data_count, data_json, training_ready_status]
|
| 1641 |
+
)
|
| 1642 |
+
|
| 1643 |
+
# Load data on startup
|
| 1644 |
+
demo.load(
|
| 1645 |
+
fn=load_training_data,
|
| 1646 |
+
outputs=[data_preview, data_count, data_json, training_ready_status]
|
| 1647 |
+
)
|
| 1648 |
+
|
| 1649 |
+
# Tab 2: Synthetic Data Generation
|
| 1650 |
+
with gr.Tab("🤖 Synthetic Data"):
|
| 1651 |
+
gr.Markdown("""
|
| 1652 |
+
# Generate Training Data Automatically
|
| 1653 |
+
|
| 1654 |
+
Use GPT-4 or Claude to automatically generate high-quality financial Q&A pairs!
|
| 1655 |
+
**No manual writing required - just configure and generate.**
|
| 1656 |
+
""")
|
| 1657 |
+
|
| 1658 |
+
with gr.Row():
|
| 1659 |
+
with gr.Column():
|
| 1660 |
+
gr.Markdown("#### 🔧 Generation Settings")
|
| 1661 |
+
|
| 1662 |
+
syn_provider = gr.Radio(
|
| 1663 |
+
choices=["openai", "anthropic"],
|
| 1664 |
+
value="openai",
|
| 1665 |
+
label="API Provider",
|
| 1666 |
+
info="Choose which LLM to use for generation"
|
| 1667 |
+
)
|
| 1668 |
+
|
| 1669 |
+
syn_num_examples = gr.Slider(
|
| 1670 |
+
minimum=5, maximum=100, value=20, step=5,
|
| 1671 |
+
label="Number of Examples to Generate",
|
| 1672 |
+
info="Start with 20, then increase"
|
| 1673 |
+
)
|
| 1674 |
+
|
| 1675 |
+
syn_difficulty = gr.Radio(
|
| 1676 |
+
choices=["beginner", "intermediate", "advanced", "mixed"],
|
| 1677 |
+
value="mixed",
|
| 1678 |
+
label="Difficulty Level",
|
| 1679 |
+
info="Mixed creates diverse questions"
|
| 1680 |
+
)
|
| 1681 |
+
|
| 1682 |
+
syn_use_scenarios = gr.Checkbox(
|
| 1683 |
+
label="Use Realistic Scenarios",
|
| 1684 |
+
value=False,
|
| 1685 |
+
info="Generate questions with specific user contexts (age, income, etc.)"
|
| 1686 |
+
)
|
| 1687 |
+
|
| 1688 |
+
gr.Markdown("---")
|
| 1689 |
+
gr.Markdown("#### 📋 Available Topics")
|
| 1690 |
+
|
| 1691 |
+
syn_topics_display = gr.Textbox(
|
| 1692 |
+
label="Default Topics (leave Custom Topics empty to use these)",
|
| 1693 |
+
value=get_available_topics(),
|
| 1694 |
+
lines=8,
|
| 1695 |
+
max_lines=12,
|
| 1696 |
+
interactive=False
|
| 1697 |
+
)
|
| 1698 |
+
|
| 1699 |
+
syn_custom_topics = gr.Textbox(
|
| 1700 |
+
label="Custom Topics (Optional)",
|
| 1701 |
+
placeholder="401k Planning, Crypto Trading, Home Buying (comma-separated)",
|
| 1702 |
+
lines=3,
|
| 1703 |
+
info="Leave empty to use all default topics"
|
| 1704 |
+
)
|
| 1705 |
+
|
| 1706 |
+
gr.Markdown("---")
|
| 1707 |
+
|
| 1708 |
+
syn_generate_btn = gr.Button("🚀 Generate Data", variant="primary", size="lg")
|
| 1709 |
+
syn_status = gr.Markdown()
|
| 1710 |
+
|
| 1711 |
+
with gr.Column():
|
| 1712 |
+
gr.Markdown("#### 📊 Generation Log")
|
| 1713 |
+
syn_log = gr.Textbox(
|
| 1714 |
+
label="Progress & Preview",
|
| 1715 |
+
lines=20,
|
| 1716 |
+
max_lines=25,
|
| 1717 |
+
interactive=False,
|
| 1718 |
+
show_copy_button=True
|
| 1719 |
+
)
|
| 1720 |
+
|
| 1721 |
+
gr.Markdown("---")
|
| 1722 |
+
gr.Markdown("#### 💡 Tips & Info")
|
| 1723 |
+
syn_tips = gr.Textbox(
|
| 1724 |
+
label="Important Information",
|
| 1725 |
+
value="""💡 Tips:
|
| 1726 |
+
• Make sure you've added your OpenAI or Anthropic API key in Settings tab
|
| 1727 |
+
• Generated data is automatically added to your training dataset
|
| 1728 |
+
• Each example costs ~$0.01-0.02 in API credits
|
| 1729 |
+
• Quality is very high - often better than manual examples
|
| 1730 |
+
• Start with 20 examples to test, then generate more
|
| 1731 |
+
• Mix difficulties for best results
|
| 1732 |
+
• Scenario mode creates more realistic, personalized questions
|
| 1733 |
+
|
| 1734 |
+
⚠️ API Key Required:
|
| 1735 |
+
This feature requires an OpenAI or Anthropic API key.
|
| 1736 |
+
Set it in the Settings tab before generating.""",
|
| 1737 |
+
lines=8,
|
| 1738 |
+
max_lines=12,
|
| 1739 |
+
interactive=False
|
| 1740 |
+
)
|
| 1741 |
+
|
| 1742 |
+
# Wire up synthetic data generation
|
| 1743 |
+
syn_generate_btn.click(
|
| 1744 |
+
fn=generate_synthetic_data,
|
| 1745 |
+
inputs=[syn_provider, syn_num_examples, syn_difficulty, syn_use_scenarios, syn_custom_topics],
|
| 1746 |
+
outputs=[syn_log, syn_status]
|
| 1747 |
+
)
|
| 1748 |
+
|
| 1749 |
+
# Tab 3: HuggingFace Datasets
|
| 1750 |
+
with gr.Tab("📦 HuggingFace Datasets"):
|
| 1751 |
+
gr.Markdown("""
|
| 1752 |
+
# Load Datasets from HuggingFace
|
| 1753 |
+
|
| 1754 |
+
Import high-quality financial datasets directly from HuggingFace!
|
| 1755 |
+
**Includes transaction categorization, financial Q&A, and more.**
|
| 1756 |
+
""")
|
| 1757 |
+
|
| 1758 |
+
with gr.Row():
|
| 1759 |
+
with gr.Column():
|
| 1760 |
+
gr.Markdown("#### 📋 Known Datasets")
|
| 1761 |
+
|
| 1762 |
+
hf_datasets_list = gr.Textbox(
|
| 1763 |
+
label="Available Financial Datasets",
|
| 1764 |
+
value=list_hf_datasets(),
|
| 1765 |
+
lines=10,
|
| 1766 |
+
max_lines=15,
|
| 1767 |
+
interactive=False
|
| 1768 |
+
)
|
| 1769 |
+
|
| 1770 |
+
gr.Markdown("---")
|
| 1771 |
+
gr.Markdown("#### 🔧 Load Settings")
|
| 1772 |
+
|
| 1773 |
+
hf_dataset_name = gr.Radio(
|
| 1774 |
+
choices=["financial-alpaca", "fingpt-finred", "finance-qa-10k", "Custom Path"],
|
| 1775 |
+
value="financial-alpaca",
|
| 1776 |
+
label="Select Dataset",
|
| 1777 |
+
info="Choose from publicly accessible datasets or use custom path"
|
| 1778 |
+
)
|
| 1779 |
+
|
| 1780 |
+
hf_custom_path = gr.Textbox(
|
| 1781 |
+
label="Custom Dataset Path (if 'Custom Path' selected)",
|
| 1782 |
+
placeholder="username/dataset-name",
|
| 1783 |
+
info="Full HuggingFace dataset path"
|
| 1784 |
+
)
|
| 1785 |
+
|
| 1786 |
+
hf_split = gr.Radio(
|
| 1787 |
+
choices=["train", "test", "validation"],
|
| 1788 |
+
value="train",
|
| 1789 |
+
label="Dataset Split",
|
| 1790 |
+
info="Which split to load"
|
| 1791 |
+
)
|
| 1792 |
+
|
| 1793 |
+
hf_max_examples = gr.Number(
|
| 1794 |
+
label="Max Examples to Load (optional)",
|
| 1795 |
+
value=None,
|
| 1796 |
+
placeholder="Leave empty for all",
|
| 1797 |
+
info="Limit number of examples (helps with large datasets)"
|
| 1798 |
+
)
|
| 1799 |
+
|
| 1800 |
+
gr.Markdown("---")
|
| 1801 |
+
|
| 1802 |
+
with gr.Row():
|
| 1803 |
+
hf_preview_btn = gr.Button("👁️ Preview Dataset", variant="secondary")
|
| 1804 |
+
hf_load_btn = gr.Button("📥 Load Dataset", variant="primary", size="lg")
|
| 1805 |
+
|
| 1806 |
+
hf_status = gr.Markdown()
|
| 1807 |
+
|
| 1808 |
+
with gr.Column():
|
| 1809 |
+
gr.Markdown("#### 📊 Dataset Info & Logs")
|
| 1810 |
+
|
| 1811 |
+
hf_preview = gr.Textbox(
|
| 1812 |
+
label="Dataset Preview",
|
| 1813 |
+
lines=10,
|
| 1814 |
+
max_lines=15,
|
| 1815 |
+
interactive=False,
|
| 1816 |
+
placeholder="Click 'Preview Dataset' to see sample data...",
|
| 1817 |
+
show_copy_button=True
|
| 1818 |
+
)
|
| 1819 |
+
|
| 1820 |
+
gr.Markdown("---")
|
| 1821 |
+
|
| 1822 |
+
hf_log = gr.Textbox(
|
| 1823 |
+
label="Loading Log",
|
| 1824 |
+
lines=12,
|
| 1825 |
+
max_lines=18,
|
| 1826 |
+
interactive=False,
|
| 1827 |
+
placeholder="Loading progress will appear here...",
|
| 1828 |
+
show_copy_button=True
|
| 1829 |
+
)
|
| 1830 |
+
|
| 1831 |
+
gr.Markdown("---")
|
| 1832 |
+
gr.Markdown("#### 💡 Tips & Info")
|
| 1833 |
+
hf_tips = gr.Textbox(
|
| 1834 |
+
label="Important Information",
|
| 1835 |
+
value="""💡 Available Datasets:
|
| 1836 |
+
|
| 1837 |
+
• financial-alpaca (52K examples)
|
| 1838 |
+
Pre-built financial Q&A in Alpaca format - publicly accessible
|
| 1839 |
+
|
| 1840 |
+
• fingpt-finred
|
| 1841 |
+
Financial relation extraction dataset - publicly accessible
|
| 1842 |
+
|
| 1843 |
+
• finance-qa-10k
|
| 1844 |
+
Q&A from 10-K SEC filings - publicly accessible
|
| 1845 |
+
|
| 1846 |
+
💡 Tips:
|
| 1847 |
+
• Preview datasets before loading to understand structure
|
| 1848 |
+
• Large datasets can be limited using Max Examples
|
| 1849 |
+
• All data is automatically quality-validated before adding
|
| 1850 |
+
• These datasets are PUBLIC and don't require special access
|
| 1851 |
+
|
| 1852 |
+
🔑 Authentication:
|
| 1853 |
+
Your HuggingFace token is used automatically from Settings tab.
|
| 1854 |
+
Some private/gated datasets may require accepting terms on HuggingFace.
|
| 1855 |
+
|
| 1856 |
+
📚 Finding More Datasets:
|
| 1857 |
+
Browse: https://huggingface.co/datasets
|
| 1858 |
+
Search: "finance", "financial", "investment", "trading\"""",
|
| 1859 |
+
lines=12,
|
| 1860 |
+
max_lines=18,
|
| 1861 |
+
interactive=False
|
| 1862 |
+
)
|
| 1863 |
+
|
| 1864 |
+
# Wire up HuggingFace dataset loading
|
| 1865 |
+
hf_preview_btn.click(
|
| 1866 |
+
fn=preview_hf_dataset,
|
| 1867 |
+
inputs=hf_custom_path,
|
| 1868 |
+
outputs=hf_preview
|
| 1869 |
+
)
|
| 1870 |
+
|
| 1871 |
+
hf_load_btn.click(
|
| 1872 |
+
fn=load_hf_dataset,
|
| 1873 |
+
inputs=[hf_custom_path, hf_dataset_name, hf_max_examples, hf_split],
|
| 1874 |
+
outputs=[hf_log, hf_status]
|
| 1875 |
+
)
|
| 1876 |
+
|
| 1877 |
+
# Tab 4: Training Configuration
|
| 1878 |
+
with gr.Tab("⚙️ Training"):
|
| 1879 |
+
gr.Markdown("### Select Model and Configure Training")
|
| 1880 |
+
|
| 1881 |
+
with gr.Row():
|
| 1882 |
+
with gr.Column():
|
| 1883 |
+
gr.Markdown("#### 💻 Training Mode")
|
| 1884 |
+
|
| 1885 |
+
training_mode = gr.Radio(
|
| 1886 |
+
choices=["Local GPU", "Cloud GPU (RunPod)"],
|
| 1887 |
+
value="Cloud GPU (RunPod)",
|
| 1888 |
+
label="Where to Train",
|
| 1889 |
+
info="Local requires NVIDIA GPU. Cloud uses RunPod (pay per minute)."
|
| 1890 |
+
)
|
| 1891 |
+
|
| 1892 |
+
cloud_cost_estimate = gr.Markdown("**Estimated Cost:** Select model to see pricing")
|
| 1893 |
+
|
| 1894 |
+
gr.Markdown("---")
|
| 1895 |
+
gr.Markdown("#### 🤖 Model Selection")
|
| 1896 |
+
|
| 1897 |
+
model_choices = model_registry.get_model_choices_for_gui()
|
| 1898 |
+
model_selector = gr.Dropdown(
|
| 1899 |
+
choices=model_choices,
|
| 1900 |
+
value=model_choices[0], # Default to first model (Qwen 3 30B)
|
| 1901 |
+
label="Select Model",
|
| 1902 |
+
info="Choose which model to train"
|
| 1903 |
+
)
|
| 1904 |
+
|
| 1905 |
+
model_info_display = gr.Markdown()
|
| 1906 |
+
|
| 1907 |
+
with gr.Row():
|
| 1908 |
+
check_ollama_btn = gr.Button("🔍 Check Ollama Status", variant="secondary", size="sm")
|
| 1909 |
+
|
| 1910 |
+
ollama_status_display = gr.Markdown()
|
| 1911 |
+
|
| 1912 |
+
gr.Markdown("---")
|
| 1913 |
+
gr.Markdown("#### Training Configuration")
|
| 1914 |
+
|
| 1915 |
+
lora_rank = gr.Slider(
|
| 1916 |
+
minimum=4, maximum=64, value=16, step=4,
|
| 1917 |
+
label="LoRA Rank (Higher = More capacity, more memory)",
|
| 1918 |
+
info="Recommended: 16 for 30B model"
|
| 1919 |
+
)
|
| 1920 |
+
|
| 1921 |
+
learning_rate = gr.Slider(
|
| 1922 |
+
minimum=1e-5, maximum=5e-4, value=1e-4, step=1e-5,
|
| 1923 |
+
label="Learning Rate",
|
| 1924 |
+
info="Recommended: 1e-4 for large models"
|
| 1925 |
+
)
|
| 1926 |
+
|
| 1927 |
+
num_epochs = gr.Slider(
|
| 1928 |
+
minimum=1, maximum=10, value=3, step=1,
|
| 1929 |
+
label="Number of Epochs",
|
| 1930 |
+
info="Start with 1 epoch to test"
|
| 1931 |
+
)
|
| 1932 |
+
|
| 1933 |
+
batch_size = gr.Slider(
|
| 1934 |
+
minimum=1, maximum=4, value=1, step=1,
|
| 1935 |
+
label="Batch Size",
|
| 1936 |
+
info="Keep at 1 for 30B model"
|
| 1937 |
+
)
|
| 1938 |
+
|
| 1939 |
+
grad_accum = gr.Slider(
|
| 1940 |
+
minimum=1, maximum=32, value=16, step=1,
|
| 1941 |
+
label="Gradient Accumulation Steps",
|
| 1942 |
+
info="Effective batch = batch_size × grad_accum"
|
| 1943 |
+
)
|
| 1944 |
+
|
| 1945 |
+
gr.Markdown("---")
|
| 1946 |
+
|
| 1947 |
+
start_train_btn = gr.Button("🚀 Start Training", variant="primary", size="lg")
|
| 1948 |
+
training_status_text = gr.Markdown()
|
| 1949 |
+
|
| 1950 |
+
with gr.Column():
|
| 1951 |
+
gr.Markdown("#### Training Progress & Logs")
|
| 1952 |
+
training_log = gr.Textbox(
|
| 1953 |
+
label="Training Log",
|
| 1954 |
+
lines=20,
|
| 1955 |
+
max_lines=25,
|
| 1956 |
+
interactive=False,
|
| 1957 |
+
show_copy_button=True
|
| 1958 |
+
)
|
| 1959 |
+
|
| 1960 |
+
gr.Markdown("""
|
| 1961 |
+
**💡 Tips:**
|
| 1962 |
+
- First training will download ~16GB model
|
| 1963 |
+
- Monitor with TensorBoard: `tensorboard --logdir models/financial_advisor/logs`
|
| 1964 |
+
- Training 30B model takes 30-60 min per epoch
|
| 1965 |
+
- GPU needs ~18GB VRAM minimum
|
| 1966 |
+
""")
|
| 1967 |
+
|
| 1968 |
+
# Wire up model selection
|
| 1969 |
+
model_selector.change(
|
| 1970 |
+
fn=get_model_info,
|
| 1971 |
+
inputs=model_selector,
|
| 1972 |
+
outputs=model_info_display
|
| 1973 |
+
)
|
| 1974 |
+
|
| 1975 |
+
check_ollama_btn.click(
|
| 1976 |
+
fn=check_ollama_status,
|
| 1977 |
+
outputs=ollama_status_display
|
| 1978 |
+
)
|
| 1979 |
+
|
| 1980 |
+
# Load default model info on startup
|
| 1981 |
+
demo.load(
|
| 1982 |
+
fn=get_model_info,
|
| 1983 |
+
inputs=model_selector,
|
| 1984 |
+
outputs=model_info_display
|
| 1985 |
+
)
|
| 1986 |
+
|
| 1987 |
+
# Wire up training
|
| 1988 |
+
start_train_btn.click(
|
| 1989 |
+
fn=start_training,
|
| 1990 |
+
inputs=[lora_rank, learning_rate, num_epochs, batch_size, grad_accum],
|
| 1991 |
+
outputs=[training_log, training_status_text]
|
| 1992 |
+
)
|
| 1993 |
+
|
| 1994 |
+
# Tab 3: Testing
|
| 1995 |
+
with gr.Tab("🧪 Test Model"):
|
| 1996 |
+
gr.Markdown("### Test Your Trained Financial Advisor")
|
| 1997 |
+
|
| 1998 |
+
with gr.Row():
|
| 1999 |
+
with gr.Column():
|
| 2000 |
+
load_model_btn = gr.Button("📥 Load Trained Model", variant="secondary")
|
| 2001 |
+
load_status = gr.Markdown()
|
| 2002 |
+
|
| 2003 |
+
gr.Markdown("---")
|
| 2004 |
+
|
| 2005 |
+
test_question = gr.Textbox(
|
| 2006 |
+
label="Ask Your Financial Advisor",
|
| 2007 |
+
placeholder="Should I pay off my student loans or invest in my 401k?",
|
| 2008 |
+
lines=4
|
| 2009 |
+
)
|
| 2010 |
+
|
| 2011 |
+
test_btn = gr.Button("💬 Get Advice", variant="primary", size="lg")
|
| 2012 |
+
|
| 2013 |
+
gr.Markdown("#### Example Questions:")
|
| 2014 |
+
gr.Markdown("""
|
| 2015 |
+
- What's the difference between a Roth IRA and Traditional IRA?
|
| 2016 |
+
- How much should I have in my emergency fund?
|
| 2017 |
+
- Should I invest in index funds or individual stocks?
|
| 2018 |
+
- What is dollar-cost averaging?
|
| 2019 |
+
- How do I start investing with only $100 per month?
|
| 2020 |
+
""")
|
| 2021 |
+
|
| 2022 |
+
with gr.Column():
|
| 2023 |
+
gr.Markdown("#### Financial Advisor Response")
|
| 2024 |
+
test_response = gr.Textbox(
|
| 2025 |
+
label="Response",
|
| 2026 |
+
lines=15,
|
| 2027 |
+
max_lines=20,
|
| 2028 |
+
interactive=False,
|
| 2029 |
+
show_copy_button=True
|
| 2030 |
+
)
|
| 2031 |
+
|
| 2032 |
+
# Wire up testing
|
| 2033 |
+
load_model_btn.click(
|
| 2034 |
+
fn=load_existing_model,
|
| 2035 |
+
outputs=load_status
|
| 2036 |
+
)
|
| 2037 |
+
|
| 2038 |
+
test_btn.click(
|
| 2039 |
+
fn=test_financial_advisor,
|
| 2040 |
+
inputs=test_question,
|
| 2041 |
+
outputs=test_response
|
| 2042 |
+
)
|
| 2043 |
+
|
| 2044 |
+
# Tab 6: Evaluation & Comparison
|
| 2045 |
+
with gr.Tab("📊 Evaluation"):
|
| 2046 |
+
gr.Markdown("### Model Evaluation & Comparison")
|
| 2047 |
+
|
| 2048 |
+
with gr.Row():
|
| 2049 |
+
with gr.Column():
|
| 2050 |
+
gr.Markdown("#### 📋 Evaluation History")
|
| 2051 |
+
|
| 2052 |
+
history_refresh_btn = gr.Button("🔄 Refresh History", variant="secondary")
|
| 2053 |
+
|
| 2054 |
+
eval_history_display = gr.Textbox(
|
| 2055 |
+
label="Recent Evaluations",
|
| 2056 |
+
lines=15,
|
| 2057 |
+
max_lines=20,
|
| 2058 |
+
interactive=False,
|
| 2059 |
+
show_copy_button=True,
|
| 2060 |
+
placeholder="Click 'Refresh History' to see evaluation history..."
|
| 2061 |
+
)
|
| 2062 |
+
|
| 2063 |
+
gr.Markdown("---")
|
| 2064 |
+
gr.Markdown("#### 🔍 Latest Evaluation Details")
|
| 2065 |
+
|
| 2066 |
+
latest_eval_btn = gr.Button("📄 View Latest Evaluation", variant="secondary")
|
| 2067 |
+
|
| 2068 |
+
latest_eval_display = gr.Textbox(
|
| 2069 |
+
label="Latest Evaluation Report",
|
| 2070 |
+
lines=15,
|
| 2071 |
+
max_lines=20,
|
| 2072 |
+
interactive=False,
|
| 2073 |
+
show_copy_button=True,
|
| 2074 |
+
placeholder="Click to view detailed evaluation report..."
|
| 2075 |
+
)
|
| 2076 |
+
|
| 2077 |
+
with gr.Column():
|
| 2078 |
+
gr.Markdown("#### 📈 Model Comparison")
|
| 2079 |
+
|
| 2080 |
+
num_models_compare = gr.Slider(
|
| 2081 |
+
minimum=2,
|
| 2082 |
+
maximum=10,
|
| 2083 |
+
value=3,
|
| 2084 |
+
step=1,
|
| 2085 |
+
label="Number of Models to Compare",
|
| 2086 |
+
info="Compare recent model evaluations"
|
| 2087 |
+
)
|
| 2088 |
+
|
| 2089 |
+
compare_btn = gr.Button("⚖️ Compare Models", variant="primary", size="lg")
|
| 2090 |
+
|
| 2091 |
+
comparison_display = gr.Textbox(
|
| 2092 |
+
label="Model Comparison Report",
|
| 2093 |
+
lines=20,
|
| 2094 |
+
max_lines=25,
|
| 2095 |
+
interactive=False,
|
| 2096 |
+
show_copy_button=True,
|
| 2097 |
+
placeholder="Click 'Compare Models' to see side-by-side comparison..."
|
| 2098 |
+
)
|
| 2099 |
+
|
| 2100 |
+
gr.Markdown("---")
|
| 2101 |
+
gr.Markdown("""
|
| 2102 |
+
**💡 Tips:**
|
| 2103 |
+
- Evaluations are run automatically after training
|
| 2104 |
+
- Compare metrics across different training runs
|
| 2105 |
+
- Use comparison to find the best model
|
| 2106 |
+
- Detailed reports saved in `models/financial_advisor/evaluation_results/`
|
| 2107 |
+
""")
|
| 2108 |
+
|
| 2109 |
+
# Wire up evaluation functions
|
| 2110 |
+
history_refresh_btn.click(
|
| 2111 |
+
fn=load_evaluation_history,
|
| 2112 |
+
outputs=eval_history_display
|
| 2113 |
+
)
|
| 2114 |
+
|
| 2115 |
+
latest_eval_btn.click(
|
| 2116 |
+
fn=view_latest_evaluation,
|
| 2117 |
+
outputs=latest_eval_display
|
| 2118 |
+
)
|
| 2119 |
+
|
| 2120 |
+
compare_btn.click(
|
| 2121 |
+
fn=compare_models,
|
| 2122 |
+
inputs=num_models_compare,
|
| 2123 |
+
outputs=comparison_display
|
| 2124 |
+
)
|
| 2125 |
+
|
| 2126 |
+
# Load history on startup
|
| 2127 |
+
demo.load(
|
| 2128 |
+
fn=load_evaluation_history,
|
| 2129 |
+
outputs=eval_history_display
|
| 2130 |
+
)
|
| 2131 |
+
|
| 2132 |
+
# Tab 7: RunPod Cloud Deployment
|
| 2133 |
+
with gr.Tab("☁️ RunPod"):
|
| 2134 |
+
gr.Markdown("""
|
| 2135 |
+
# Cloud GPU Deployment with RunPod
|
| 2136 |
+
|
| 2137 |
+
Deploy and train on powerful cloud GPUs without any manual setup!
|
| 2138 |
+
**One-click deployment to RunPod cloud GPUs - fully automated.**
|
| 2139 |
+
|
| 2140 |
+
## 🎯 What This Does
|
| 2141 |
+
|
| 2142 |
+
This tab lets you:
|
| 2143 |
+
1. **Create cloud GPU pods** - Get a powerful GPU in the cloud
|
| 2144 |
+
2. **Auto-deploy your code** - Your AURA app runs on the cloud GPU
|
| 2145 |
+
3. **Train remotely** - Use the same GUI, but on cloud hardware
|
| 2146 |
+
|
| 2147 |
+
## ⚡ Quick Start
|
| 2148 |
+
|
| 2149 |
+
**Before you start**, make sure you have:
|
| 2150 |
+
- ✅ Added RunPod API key in **⚙️ Settings** tab
|
| 2151 |
+
- ✅ Prepared training data in **📚 Training Data** tab
|
| 2152 |
+
|
| 2153 |
+
Then:
|
| 2154 |
+
1. Configure pod settings below (defaults are good)
|
| 2155 |
+
2. Click "🚀 Create & Deploy Pod"
|
| 2156 |
+
3. Wait ~5 minutes for setup
|
| 2157 |
+
4. Access the cloud GUI via the provided URL
|
| 2158 |
+
5. Train using the cloud GPU!
|
| 2159 |
+
""")
|
| 2160 |
+
|
| 2161 |
+
with gr.Row():
|
| 2162 |
+
with gr.Column():
|
| 2163 |
+
gr.Markdown("#### 🔧 Pod Configuration")
|
| 2164 |
+
|
| 2165 |
+
runpod_pod_name = gr.Textbox(
|
| 2166 |
+
label="Pod Name",
|
| 2167 |
+
value="aura-training-pod",
|
| 2168 |
+
placeholder="my-training-pod",
|
| 2169 |
+
info="Name for your RunPod instance"
|
| 2170 |
+
)
|
| 2171 |
+
|
| 2172 |
+
runpod_gpu_type = gr.Dropdown(
|
| 2173 |
+
choices=[
|
| 2174 |
+
"NVIDIA GeForce RTX 4090",
|
| 2175 |
+
"NVIDIA GeForce RTX 3090",
|
| 2176 |
+
"NVIDIA A100 40GB PCIe",
|
| 2177 |
+
"NVIDIA A100 80GB PCIe",
|
| 2178 |
+
"NVIDIA H100 80GB HBM3",
|
| 2179 |
+
],
|
| 2180 |
+
value="NVIDIA GeForce RTX 4090",
|
| 2181 |
+
label="GPU Type",
|
| 2182 |
+
info="RTX 4090 recommended for best value"
|
| 2183 |
+
)
|
| 2184 |
+
|
| 2185 |
+
runpod_storage = gr.Slider(
|
| 2186 |
+
minimum=20,
|
| 2187 |
+
maximum=200,
|
| 2188 |
+
value=50,
|
| 2189 |
+
step=10,
|
| 2190 |
+
label="Storage (GB)",
|
| 2191 |
+
info="Disk space for models and data"
|
| 2192 |
+
)
|
| 2193 |
+
|
| 2194 |
+
runpod_sync_data = gr.Checkbox(
|
| 2195 |
+
label="Sync Training Data",
|
| 2196 |
+
value=True,
|
| 2197 |
+
info="Upload your local training data to the pod"
|
| 2198 |
+
)
|
| 2199 |
+
|
| 2200 |
+
gr.Markdown("---")
|
| 2201 |
+
|
| 2202 |
+
with gr.Row():
|
| 2203 |
+
runpod_create_btn = gr.Button("🚀 Create & Deploy Pod", variant="primary", size="lg")
|
| 2204 |
+
runpod_refresh_btn = gr.Button("🔄 Refresh Pods", variant="secondary")
|
| 2205 |
+
|
| 2206 |
+
runpod_status = gr.Markdown()
|
| 2207 |
+
|
| 2208 |
+
gr.Markdown("---")
|
| 2209 |
+
gr.Markdown("#### 📋 Your Pods")
|
| 2210 |
+
|
| 2211 |
+
runpod_pods_list = gr.Textbox(
|
| 2212 |
+
label="Active Pods",
|
| 2213 |
+
lines=10,
|
| 2214 |
+
max_lines=15,
|
| 2215 |
+
interactive=False,
|
| 2216 |
+
placeholder="Click 'Refresh Pods' to see your RunPod instances...",
|
| 2217 |
+
show_copy_button=True
|
| 2218 |
+
)
|
| 2219 |
+
|
| 2220 |
+
gr.Markdown("---")
|
| 2221 |
+
gr.Markdown("#### 🔧 Pod Management")
|
| 2222 |
+
|
| 2223 |
+
runpod_pod_id = gr.Textbox(
|
| 2224 |
+
label="Pod ID",
|
| 2225 |
+
placeholder="Enter Pod ID for management operations",
|
| 2226 |
+
info="Get from 'Your Pods' list above"
|
| 2227 |
+
)
|
| 2228 |
+
|
| 2229 |
+
with gr.Row():
|
| 2230 |
+
runpod_stop_btn = gr.Button("⏸️ Stop Pod", variant="secondary", size="sm")
|
| 2231 |
+
runpod_terminate_btn = gr.Button("🗑️ Terminate Pod", variant="stop", size="sm")
|
| 2232 |
+
|
| 2233 |
+
runpod_mgmt_status = gr.Markdown()
|
| 2234 |
+
|
| 2235 |
+
with gr.Column():
|
| 2236 |
+
gr.Markdown("#### 📊 Deployment Log")
|
| 2237 |
+
|
| 2238 |
+
runpod_log = gr.Textbox(
|
| 2239 |
+
label="Progress & Status",
|
| 2240 |
+
lines=15,
|
| 2241 |
+
max_lines=20,
|
| 2242 |
+
interactive=False,
|
| 2243 |
+
show_copy_button=True,
|
| 2244 |
+
placeholder="Deployment progress will appear here..."
|
| 2245 |
+
)
|
| 2246 |
+
|
| 2247 |
+
gr.Markdown("---")
|
| 2248 |
+
gr.Markdown("#### 🔗 Connection Info")
|
| 2249 |
+
|
| 2250 |
+
runpod_connection_info = gr.Markdown("Select a pod and click 'Get Connection Info'")
|
| 2251 |
+
|
| 2252 |
+
runpod_connect_btn = gr.Button("🔗 Get Connection Info", variant="secondary")
|
| 2253 |
+
|
| 2254 |
+
gr.Markdown("---")
|
| 2255 |
+
gr.Markdown("""
|
| 2256 |
+
**💰 Cost Estimates:**
|
| 2257 |
+
- RTX 4090: ~$0.69/hour (Best Value)
|
| 2258 |
+
- RTX 3090: ~$0.44/hour (Budget)
|
| 2259 |
+
- A100 40GB: ~$1.39/hour (Production)
|
| 2260 |
+
|
| 2261 |
+
**📝 What Gets Deployed:**
|
| 2262 |
+
- ✅ All your code files
|
| 2263 |
+
- ✅ Your training data (if "Sync Data" checked)
|
| 2264 |
+
- ✅ Python dependencies (auto-installed)
|
| 2265 |
+
- ✅ Same GUI you're using now
|
| 2266 |
+
|
| 2267 |
+
**After Deployment:**
|
| 2268 |
+
- Access GUI in browser (URL provided below)
|
| 2269 |
+
- Train on the cloud GPU (same interface)
|
| 2270 |
+
- Stop pod when done to save money
|
| 2271 |
+
- Your trained models stay on the pod
|
| 2272 |
+
|
| 2273 |
+
**🔑 Get RunPod API Key:**
|
| 2274 |
+
1. Go to https://www.runpod.io/console/user/settings
|
| 2275 |
+
2. Click "API Keys" → "Create API Key"
|
| 2276 |
+
3. Copy the key
|
| 2277 |
+
4. Add to **⚙️ Settings** tab above
|
| 2278 |
+
5. Come back here and create a pod!
|
| 2279 |
+
""")
|
| 2280 |
+
|
| 2281 |
+
# Wire up RunPod functions
|
| 2282 |
+
runpod_create_btn.click(
|
| 2283 |
+
fn=create_runpod_pod,
|
| 2284 |
+
inputs=[runpod_pod_name, runpod_gpu_type, runpod_storage, runpod_sync_data],
|
| 2285 |
+
outputs=[runpod_log, runpod_status]
|
| 2286 |
+
)
|
| 2287 |
+
|
| 2288 |
+
runpod_refresh_btn.click(
|
| 2289 |
+
fn=list_runpod_pods,
|
| 2290 |
+
outputs=runpod_pods_list
|
| 2291 |
+
)
|
| 2292 |
+
|
| 2293 |
+
runpod_stop_btn.click(
|
| 2294 |
+
fn=stop_runpod_pod,
|
| 2295 |
+
inputs=runpod_pod_id,
|
| 2296 |
+
outputs=runpod_mgmt_status
|
| 2297 |
+
)
|
| 2298 |
+
|
| 2299 |
+
runpod_terminate_btn.click(
|
| 2300 |
+
fn=terminate_runpod_pod,
|
| 2301 |
+
inputs=runpod_pod_id,
|
| 2302 |
+
outputs=runpod_mgmt_status
|
| 2303 |
+
)
|
| 2304 |
+
|
| 2305 |
+
runpod_connect_btn.click(
|
| 2306 |
+
fn=get_pod_connection_info,
|
| 2307 |
+
inputs=runpod_pod_id,
|
| 2308 |
+
outputs=runpod_connection_info
|
| 2309 |
+
)
|
| 2310 |
+
|
| 2311 |
+
# Tab 4: Help
|
| 2312 |
+
with gr.Tab("❓ Help"):
|
| 2313 |
+
gr.Markdown("""
|
| 2314 |
+
# Getting Started Guide
|
| 2315 |
+
|
| 2316 |
+
## Step 1: Prepare Training Data
|
| 2317 |
+
1. Go to **Training Data** tab
|
| 2318 |
+
2. Review the sample financial Q&A pairs
|
| 2319 |
+
3. Add your own examples using the form
|
| 2320 |
+
4. Aim for at least 50-100 high-quality examples
|
| 2321 |
+
|
| 2322 |
+
## Step 2: Configure Training
|
| 2323 |
+
1. Go to **Training** tab
|
| 2324 |
+
2. Adjust settings (defaults are good to start):
|
| 2325 |
+
- LoRA Rank: 16 (higher = more capacity)
|
| 2326 |
+
- Learning Rate: 1e-4 (standard for large models)
|
| 2327 |
+
- Epochs: 1-3 (start with 1 to test)
|
| 2328 |
+
3. Click **Start Training**
|
| 2329 |
+
4. Wait 30-60 minutes per epoch
|
| 2330 |
+
|
| 2331 |
+
## Step 3: Test Your Model
|
| 2332 |
+
1. Go to **Test Model** tab
|
| 2333 |
+
2. Click **Load Trained Model**
|
| 2334 |
+
3. Ask financial questions
|
| 2335 |
+
4. Get expert advice!
|
| 2336 |
+
|
| 2337 |
+
## Requirements
|
| 2338 |
+
- **GPU**: NVIDIA GPU with 18GB+ VRAM (RTX 3090, 4090, A100, etc.)
|
| 2339 |
+
- **RAM**: 32GB+ system RAM recommended
|
| 2340 |
+
- **Storage**: 30GB free space for model
|
| 2341 |
+
- **Internet**: Fast connection for first-time model download
|
| 2342 |
+
- **HuggingFace Token**: Required for model access (set in Settings tab)
|
| 2343 |
+
|
| 2344 |
+
## Troubleshooting
|
| 2345 |
+
|
| 2346 |
+
### Out of Memory (OOM)
|
| 2347 |
+
- Reduce batch_size to 1
|
| 2348 |
+
- Increase gradient_accumulation_steps
|
| 2349 |
+
- Close other applications
|
| 2350 |
+
|
| 2351 |
+
### Slow Training
|
| 2352 |
+
- This is normal for 30B model
|
| 2353 |
+
- Each epoch takes 30-60 minutes
|
| 2354 |
+
- Be patient!
|
| 2355 |
+
|
| 2356 |
+
### Import Errors
|
| 2357 |
+
```bash
|
| 2358 |
+
pip install torch transformers peft accelerate bitsandbytes gradio cryptography
|
| 2359 |
+
```
|
| 2360 |
+
|
| 2361 |
+
### HuggingFace Authentication Error
|
| 2362 |
+
- Go to **Settings** tab
|
| 2363 |
+
- Add your HuggingFace token
|
| 2364 |
+
- Click **Save All Keys**
|
| 2365 |
+
- Get token at: https://huggingface.co/settings/tokens
|
| 2366 |
+
|
| 2367 |
+
### Model Not Learning
|
| 2368 |
+
- Check learning rate (try 1e-4 to 5e-4)
|
| 2369 |
+
- Verify training data quality
|
| 2370 |
+
- Train for more epochs
|
| 2371 |
+
|
| 2372 |
+
## Advanced Tips
|
| 2373 |
+
1. **More Data = Better Results**: Aim for 500+ examples
|
| 2374 |
+
2. **Diverse Topics**: Cover various financial areas
|
| 2375 |
+
3. **Quality > Quantity**: Accurate answers matter
|
| 2376 |
+
4. **Monitor TensorBoard**: Track loss curves
|
| 2377 |
+
5. **Test Regularly**: Evaluate after each epoch
|
| 2378 |
+
|
| 2379 |
+
## Support
|
| 2380 |
+
- Check logs in Training tab
|
| 2381 |
+
- Review error messages carefully
|
| 2382 |
+
- Verify GPU has enough memory
|
| 2383 |
+
- Ensure all dependencies installed
|
| 2384 |
+
""")
|
| 2385 |
+
|
| 2386 |
+
gr.Markdown("""
|
| 2387 |
+
---
|
| 2388 |
+
💡 **Pro Tip**: Start with the sample data and 1 epoch to test everything works, then add more data and train for 3+ epochs.
|
| 2389 |
+
""")
|
| 2390 |
+
|
| 2391 |
+
|
| 2392 |
+
if __name__ == "__main__":
|
| 2393 |
+
import argparse
|
| 2394 |
+
import os
|
| 2395 |
+
|
| 2396 |
+
# Parse command-line arguments for cloud deployment compatibility
|
| 2397 |
+
parser = argparse.ArgumentParser(description="Launch AURA AI Training Studio")
|
| 2398 |
+
parser.add_argument("--server_name", type=str, default="0.0.0.0", help="Server host")
|
| 2399 |
+
parser.add_argument("--server_port", type=int, default=int(os.getenv("PORT", 7860)), help="Server port")
|
| 2400 |
+
parser.add_argument("--share", action="store_true", help="Create public share link")
|
| 2401 |
+
args = parser.parse_args()
|
| 2402 |
+
|
| 2403 |
+
print("=" * 70)
|
| 2404 |
+
print("Financial Advisor AI Training Studio")
|
| 2405 |
+
print("=" * 70)
|
| 2406 |
+
print("\nStarting GUI server...")
|
| 2407 |
+
print(f"Server: http://{args.server_name}:{args.server_port}")
|
| 2408 |
+
if args.share:
|
| 2409 |
+
print("Share link will be generated...")
|
| 2410 |
+
print("\nPress Ctrl+C to stop the server")
|
| 2411 |
+
print("=" * 70)
|
| 2412 |
+
|
| 2413 |
+
demo.launch(
|
| 2414 |
+
server_name=args.server_name,
|
| 2415 |
+
server_port=args.server_port,
|
| 2416 |
+
share=args.share,
|
| 2417 |
+
show_error=True
|
| 2418 |
+
)
|
fine_tuning/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Fine-Tuning Module
|
| 3 |
+
|
| 4 |
+
This module provides PEFT (Parameter-Efficient Fine-Tuning) and LoRA
|
| 5 |
+
(Low-Rank Adaptation) training capabilities.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from .lora_trainer import LoRATrainer, LoRAConfig
|
| 9 |
+
from .peft_trainer import PEFTTrainer
|
| 10 |
+
from .training_loop import TrainingLoop, TrainingConfig
|
| 11 |
+
|
| 12 |
+
__all__ = ['LoRATrainer', 'LoRAConfig', 'PEFTTrainer', 'TrainingLoop', 'TrainingConfig']
|
fine_tuning/lora_trainer.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
LoRA Trainer Module
|
| 3 |
+
|
| 4 |
+
Implements Low-Rank Adaptation (LoRA) fine-tuning using HuggingFace PEFT library.
|
| 5 |
+
Supports 4-bit/8-bit quantization for efficient training on consumer hardware.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from dataclasses import dataclass, field
|
| 12 |
+
from typing import Optional, List, Dict, Any
|
| 13 |
+
import torch
|
| 14 |
+
from transformers import (
|
| 15 |
+
AutoModelForCausalLM,
|
| 16 |
+
AutoTokenizer,
|
| 17 |
+
TrainingArguments,
|
| 18 |
+
Trainer,
|
| 19 |
+
DataCollatorForLanguageModeling
|
| 20 |
+
)
|
| 21 |
+
from peft import (
|
| 22 |
+
LoraConfig,
|
| 23 |
+
get_peft_model,
|
| 24 |
+
prepare_model_for_kbit_training,
|
| 25 |
+
PeftModel
|
| 26 |
+
)
|
| 27 |
+
from datasets import Dataset
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass
|
| 31 |
+
class LoRAConfig:
|
| 32 |
+
"""LoRA configuration parameters."""
|
| 33 |
+
r: int = 8 # LoRA rank
|
| 34 |
+
lora_alpha: int = 16 # LoRA alpha (scaling factor)
|
| 35 |
+
target_modules: List[str] = field(default_factory=lambda: ["q_proj", "v_proj", "k_proj", "o_proj"])
|
| 36 |
+
lora_dropout: float = 0.05
|
| 37 |
+
bias: str = "none"
|
| 38 |
+
task_type: str = "CAUSAL_LM"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class LoRATrainer:
|
| 42 |
+
"""
|
| 43 |
+
LoRA Trainer for parameter-efficient fine-tuning of large language models.
|
| 44 |
+
|
| 45 |
+
Features:
|
| 46 |
+
- 4-bit/8-bit quantization support
|
| 47 |
+
- Automatic dataset tokenization with chat templates
|
| 48 |
+
- HuggingFace Trainer integration
|
| 49 |
+
- Checkpoint management
|
| 50 |
+
- Adapter merging for deployment
|
| 51 |
+
|
| 52 |
+
Example:
|
| 53 |
+
>>> config = LoRAConfig(r=8, lora_alpha=16)
|
| 54 |
+
>>> trainer = LoRATrainer("Qwen/Qwen2.5-7B-Instruct", config)
|
| 55 |
+
>>> trainer.load_model(use_4bit=True)
|
| 56 |
+
>>> trainer.prepare_dataset(training_data)
|
| 57 |
+
>>> trainer.train(num_epochs=3)
|
| 58 |
+
>>> trainer.save_model("./output")
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
def __init__(
|
| 62 |
+
self,
|
| 63 |
+
model_name: str,
|
| 64 |
+
lora_config: LoRAConfig,
|
| 65 |
+
output_dir: str = "./models/output"
|
| 66 |
+
):
|
| 67 |
+
"""
|
| 68 |
+
Initialize LoRA Trainer.
|
| 69 |
+
|
| 70 |
+
Args:
|
| 71 |
+
model_name: HuggingFace model path or name
|
| 72 |
+
lora_config: LoRA configuration
|
| 73 |
+
output_dir: Directory for saving checkpoints and final model
|
| 74 |
+
"""
|
| 75 |
+
self.model_name = model_name
|
| 76 |
+
self.lora_config = lora_config
|
| 77 |
+
self.output_dir = Path(output_dir)
|
| 78 |
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
| 79 |
+
|
| 80 |
+
self.model = None
|
| 81 |
+
self.tokenizer = None
|
| 82 |
+
self.train_dataset = None
|
| 83 |
+
self.eval_dataset = None
|
| 84 |
+
self.trainer = None
|
| 85 |
+
|
| 86 |
+
def load_model(
|
| 87 |
+
self,
|
| 88 |
+
use_4bit: bool = True,
|
| 89 |
+
use_8bit: bool = False,
|
| 90 |
+
device_map: str = "auto",
|
| 91 |
+
max_memory: Optional[Dict] = None
|
| 92 |
+
) -> None:
|
| 93 |
+
"""
|
| 94 |
+
Load model with LoRA adapters and optional quantization.
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
use_4bit: Use 4-bit quantization (bitsandbytes)
|
| 98 |
+
use_8bit: Use 8-bit quantization (alternative to 4-bit)
|
| 99 |
+
device_map: Device mapping strategy
|
| 100 |
+
max_memory: Maximum memory allocation per device
|
| 101 |
+
"""
|
| 102 |
+
print(f"Loading model: {self.model_name}")
|
| 103 |
+
|
| 104 |
+
# Load tokenizer
|
| 105 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 106 |
+
self.model_name,
|
| 107 |
+
trust_remote_code=True,
|
| 108 |
+
padding_side="right"
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
# Set pad token if not present
|
| 112 |
+
if self.tokenizer.pad_token is None:
|
| 113 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 114 |
+
|
| 115 |
+
# Quantization config
|
| 116 |
+
quantization_config = None
|
| 117 |
+
if use_4bit:
|
| 118 |
+
from transformers import BitsAndBytesConfig
|
| 119 |
+
quantization_config = BitsAndBytesConfig(
|
| 120 |
+
load_in_4bit=True,
|
| 121 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 122 |
+
bnb_4bit_use_double_quant=True,
|
| 123 |
+
bnb_4bit_quant_type="nf4"
|
| 124 |
+
)
|
| 125 |
+
elif use_8bit:
|
| 126 |
+
from transformers import BitsAndBytesConfig
|
| 127 |
+
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
|
| 128 |
+
|
| 129 |
+
# Load base model
|
| 130 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 131 |
+
self.model_name,
|
| 132 |
+
quantization_config=quantization_config,
|
| 133 |
+
device_map=device_map,
|
| 134 |
+
max_memory=max_memory,
|
| 135 |
+
trust_remote_code=True,
|
| 136 |
+
torch_dtype=torch.float16 if not (use_4bit or use_8bit) else None
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# Prepare for k-bit training if quantized
|
| 140 |
+
if use_4bit or use_8bit:
|
| 141 |
+
self.model = prepare_model_for_kbit_training(self.model)
|
| 142 |
+
|
| 143 |
+
# Configure LoRA
|
| 144 |
+
peft_config = LoraConfig(
|
| 145 |
+
r=self.lora_config.r,
|
| 146 |
+
lora_alpha=self.lora_config.lora_alpha,
|
| 147 |
+
target_modules=self.lora_config.target_modules,
|
| 148 |
+
lora_dropout=self.lora_config.lora_dropout,
|
| 149 |
+
bias=self.lora_config.bias,
|
| 150 |
+
task_type=self.lora_config.task_type
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Apply LoRA adapters
|
| 154 |
+
self.model = get_peft_model(self.model, peft_config)
|
| 155 |
+
|
| 156 |
+
# Print trainable parameters
|
| 157 |
+
self.model.print_trainable_parameters()
|
| 158 |
+
|
| 159 |
+
print(f"✅ Model loaded with LoRA (rank={self.lora_config.r})")
|
| 160 |
+
|
| 161 |
+
def prepare_dataset(
|
| 162 |
+
self,
|
| 163 |
+
data: List[Dict],
|
| 164 |
+
validation_split: float = 0.1,
|
| 165 |
+
max_length: int = 2048,
|
| 166 |
+
test_data: Optional[List[Dict]] = None
|
| 167 |
+
) -> None:
|
| 168 |
+
"""
|
| 169 |
+
Tokenize and prepare dataset for training.
|
| 170 |
+
|
| 171 |
+
Args:
|
| 172 |
+
data: Training data in format [{"instruction": "...", "input": "...", "output": "..."}]
|
| 173 |
+
validation_split: Fraction of data to use for validation
|
| 174 |
+
max_length: Maximum sequence length
|
| 175 |
+
test_data: Optional separate test dataset
|
| 176 |
+
"""
|
| 177 |
+
print(f"Preparing dataset: {len(data)} examples")
|
| 178 |
+
|
| 179 |
+
def format_prompt(example):
|
| 180 |
+
"""Format example using chat template."""
|
| 181 |
+
# Build conversation
|
| 182 |
+
messages = []
|
| 183 |
+
|
| 184 |
+
# System message (optional, can be customized)
|
| 185 |
+
messages.append({
|
| 186 |
+
"role": "system",
|
| 187 |
+
"content": "You are a helpful AI assistant."
|
| 188 |
+
})
|
| 189 |
+
|
| 190 |
+
# User message
|
| 191 |
+
user_content = example.get("instruction", "")
|
| 192 |
+
if example.get("input"):
|
| 193 |
+
user_content += f"\n\n{example['input']}"
|
| 194 |
+
messages.append({
|
| 195 |
+
"role": "user",
|
| 196 |
+
"content": user_content
|
| 197 |
+
})
|
| 198 |
+
|
| 199 |
+
# Assistant response
|
| 200 |
+
messages.append({
|
| 201 |
+
"role": "assistant",
|
| 202 |
+
"content": example.get("output", "")
|
| 203 |
+
})
|
| 204 |
+
|
| 205 |
+
# Apply chat template
|
| 206 |
+
try:
|
| 207 |
+
formatted = self.tokenizer.apply_chat_template(
|
| 208 |
+
messages,
|
| 209 |
+
tokenize=False,
|
| 210 |
+
add_generation_prompt=False
|
| 211 |
+
)
|
| 212 |
+
except Exception:
|
| 213 |
+
# Fallback if chat template not available
|
| 214 |
+
formatted = f"{user_content}\n\n{example.get('output', '')}"
|
| 215 |
+
|
| 216 |
+
return {"text": formatted}
|
| 217 |
+
|
| 218 |
+
# Format all examples
|
| 219 |
+
formatted_data = [format_prompt(ex) for ex in data]
|
| 220 |
+
|
| 221 |
+
# Split train/val
|
| 222 |
+
if test_data is None:
|
| 223 |
+
split_idx = int(len(formatted_data) * (1 - validation_split))
|
| 224 |
+
train_data = formatted_data[:split_idx]
|
| 225 |
+
val_data = formatted_data[split_idx:]
|
| 226 |
+
else:
|
| 227 |
+
train_data = formatted_data
|
| 228 |
+
val_data = [format_prompt(ex) for ex in test_data]
|
| 229 |
+
|
| 230 |
+
# Create datasets
|
| 231 |
+
self.train_dataset = Dataset.from_list(train_data)
|
| 232 |
+
self.eval_dataset = Dataset.from_list(val_data) if val_data else None
|
| 233 |
+
|
| 234 |
+
# Tokenization function
|
| 235 |
+
def tokenize_function(examples):
|
| 236 |
+
tokenized = self.tokenizer(
|
| 237 |
+
examples["text"],
|
| 238 |
+
truncation=True,
|
| 239 |
+
max_length=max_length,
|
| 240 |
+
padding="max_length",
|
| 241 |
+
return_tensors=None
|
| 242 |
+
)
|
| 243 |
+
tokenized["labels"] = tokenized["input_ids"].copy()
|
| 244 |
+
return tokenized
|
| 245 |
+
|
| 246 |
+
# Tokenize
|
| 247 |
+
self.train_dataset = self.train_dataset.map(
|
| 248 |
+
tokenize_function,
|
| 249 |
+
batched=True,
|
| 250 |
+
remove_columns=self.train_dataset.column_names
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
if self.eval_dataset:
|
| 254 |
+
self.eval_dataset = self.eval_dataset.map(
|
| 255 |
+
tokenize_function,
|
| 256 |
+
batched=True,
|
| 257 |
+
remove_columns=self.eval_dataset.column_names
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
print(f"✅ Dataset prepared: {len(self.train_dataset)} train, {len(self.eval_dataset) if self.eval_dataset else 0} val")
|
| 261 |
+
|
| 262 |
+
def train(
|
| 263 |
+
self,
|
| 264 |
+
num_epochs: int = 3,
|
| 265 |
+
learning_rate: float = 2e-4,
|
| 266 |
+
per_device_train_batch_size: int = 4,
|
| 267 |
+
per_device_eval_batch_size: int = 4,
|
| 268 |
+
gradient_accumulation_steps: int = 4,
|
| 269 |
+
warmup_steps: int = 100,
|
| 270 |
+
logging_steps: int = 10,
|
| 271 |
+
save_steps: int = 500,
|
| 272 |
+
eval_steps: int = 500,
|
| 273 |
+
fp16: bool = True,
|
| 274 |
+
optim: str = "paged_adamw_8bit"
|
| 275 |
+
) -> None:
|
| 276 |
+
"""
|
| 277 |
+
Train the model with LoRA.
|
| 278 |
+
|
| 279 |
+
Args:
|
| 280 |
+
num_epochs: Number of training epochs
|
| 281 |
+
learning_rate: Learning rate
|
| 282 |
+
per_device_train_batch_size: Batch size per device for training
|
| 283 |
+
per_device_eval_batch_size: Batch size per device for evaluation
|
| 284 |
+
gradient_accumulation_steps: Gradient accumulation steps
|
| 285 |
+
warmup_steps: Learning rate warmup steps
|
| 286 |
+
logging_steps: Log every N steps
|
| 287 |
+
save_steps: Save checkpoint every N steps
|
| 288 |
+
eval_steps: Evaluate every N steps
|
| 289 |
+
fp16: Use mixed precision training
|
| 290 |
+
optim: Optimizer type
|
| 291 |
+
"""
|
| 292 |
+
if self.model is None:
|
| 293 |
+
raise ValueError("Model not loaded. Call load_model() first.")
|
| 294 |
+
if self.train_dataset is None:
|
| 295 |
+
raise ValueError("Dataset not prepared. Call prepare_dataset() first.")
|
| 296 |
+
|
| 297 |
+
print(f"Starting training: {num_epochs} epochs")
|
| 298 |
+
|
| 299 |
+
# Training arguments
|
| 300 |
+
training_args = TrainingArguments(
|
| 301 |
+
output_dir=str(self.output_dir),
|
| 302 |
+
num_train_epochs=num_epochs,
|
| 303 |
+
per_device_train_batch_size=per_device_train_batch_size,
|
| 304 |
+
per_device_eval_batch_size=per_device_eval_batch_size,
|
| 305 |
+
gradient_accumulation_steps=gradient_accumulation_steps,
|
| 306 |
+
learning_rate=learning_rate,
|
| 307 |
+
warmup_steps=warmup_steps,
|
| 308 |
+
logging_steps=logging_steps,
|
| 309 |
+
save_steps=save_steps,
|
| 310 |
+
eval_steps=eval_steps if self.eval_dataset else None,
|
| 311 |
+
evaluation_strategy="steps" if self.eval_dataset else "no",
|
| 312 |
+
save_strategy="steps",
|
| 313 |
+
fp16=fp16,
|
| 314 |
+
optim=optim,
|
| 315 |
+
load_best_model_at_end=True if self.eval_dataset else False,
|
| 316 |
+
save_total_limit=3,
|
| 317 |
+
report_to=[] # Disable wandb/tensorboard by default
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
# Data collator
|
| 321 |
+
data_collator = DataCollatorForLanguageModeling(
|
| 322 |
+
tokenizer=self.tokenizer,
|
| 323 |
+
mlm=False
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
# Initialize trainer
|
| 327 |
+
self.trainer = Trainer(
|
| 328 |
+
model=self.model,
|
| 329 |
+
args=training_args,
|
| 330 |
+
train_dataset=self.train_dataset,
|
| 331 |
+
eval_dataset=self.eval_dataset,
|
| 332 |
+
data_collator=data_collator
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
# Train
|
| 336 |
+
self.trainer.train()
|
| 337 |
+
|
| 338 |
+
print("✅ Training complete!")
|
| 339 |
+
|
| 340 |
+
def save_model(self, save_path: Optional[str] = None) -> None:
|
| 341 |
+
"""
|
| 342 |
+
Save LoRA adapter weights.
|
| 343 |
+
|
| 344 |
+
Args:
|
| 345 |
+
save_path: Path to save adapters (uses output_dir if None)
|
| 346 |
+
"""
|
| 347 |
+
if save_path is None:
|
| 348 |
+
save_path = str(self.output_dir / "final_model")
|
| 349 |
+
else:
|
| 350 |
+
save_path = str(Path(save_path))
|
| 351 |
+
|
| 352 |
+
Path(save_path).mkdir(parents=True, exist_ok=True)
|
| 353 |
+
|
| 354 |
+
# Save adapter
|
| 355 |
+
self.model.save_pretrained(save_path)
|
| 356 |
+
self.tokenizer.save_pretrained(save_path)
|
| 357 |
+
|
| 358 |
+
# Save config
|
| 359 |
+
config_path = Path(save_path) / "lora_config.json"
|
| 360 |
+
with open(config_path, 'w') as f:
|
| 361 |
+
json.dump({
|
| 362 |
+
"r": self.lora_config.r,
|
| 363 |
+
"lora_alpha": self.lora_config.lora_alpha,
|
| 364 |
+
"target_modules": self.lora_config.target_modules,
|
| 365 |
+
"lora_dropout": self.lora_config.lora_dropout
|
| 366 |
+
}, f, indent=2)
|
| 367 |
+
|
| 368 |
+
print(f"✅ Model saved to: {save_path}")
|
| 369 |
+
|
| 370 |
+
def load_adapter(self, adapter_path: str) -> None:
|
| 371 |
+
"""
|
| 372 |
+
Load pre-trained LoRA adapter.
|
| 373 |
+
|
| 374 |
+
Args:
|
| 375 |
+
adapter_path: Path to adapter weights
|
| 376 |
+
"""
|
| 377 |
+
if self.model is None:
|
| 378 |
+
raise ValueError("Base model not loaded. Call load_model() first.")
|
| 379 |
+
|
| 380 |
+
print(f"Loading adapter from: {adapter_path}")
|
| 381 |
+
|
| 382 |
+
self.model = PeftModel.from_pretrained(
|
| 383 |
+
self.model,
|
| 384 |
+
adapter_path,
|
| 385 |
+
is_trainable=True
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
print("✅ Adapter loaded")
|
| 389 |
+
|
| 390 |
+
def merge_and_save(self, save_path: str) -> None:
|
| 391 |
+
"""
|
| 392 |
+
Merge LoRA weights with base model and save full model.
|
| 393 |
+
|
| 394 |
+
Args:
|
| 395 |
+
save_path: Path to save merged model
|
| 396 |
+
"""
|
| 397 |
+
print("Merging LoRA weights with base model...")
|
| 398 |
+
|
| 399 |
+
# Merge
|
| 400 |
+
merged_model = self.model.merge_and_unload()
|
| 401 |
+
|
| 402 |
+
# Save
|
| 403 |
+
Path(save_path).mkdir(parents=True, exist_ok=True)
|
| 404 |
+
merged_model.save_pretrained(save_path)
|
| 405 |
+
self.tokenizer.save_pretrained(save_path)
|
| 406 |
+
|
| 407 |
+
print(f"✅ Merged model saved to: {save_path}")
|
| 408 |
+
|
| 409 |
+
def evaluate_on_test_set(
|
| 410 |
+
self,
|
| 411 |
+
test_data: List[Dict],
|
| 412 |
+
max_samples: int = 50,
|
| 413 |
+
max_new_tokens: int = 256
|
| 414 |
+
) -> Dict[str, Any]:
|
| 415 |
+
"""
|
| 416 |
+
Evaluate model on test set.
|
| 417 |
+
|
| 418 |
+
Args:
|
| 419 |
+
test_data: Test examples
|
| 420 |
+
max_samples: Maximum number of samples to evaluate
|
| 421 |
+
max_new_tokens: Maximum tokens to generate
|
| 422 |
+
|
| 423 |
+
Returns:
|
| 424 |
+
Evaluation results dictionary
|
| 425 |
+
"""
|
| 426 |
+
import time
|
| 427 |
+
|
| 428 |
+
print(f"Evaluating on {min(len(test_data), max_samples)} test examples...")
|
| 429 |
+
|
| 430 |
+
results = {
|
| 431 |
+
"num_examples": min(len(test_data), max_samples),
|
| 432 |
+
"responses": [],
|
| 433 |
+
"avg_response_length": 0,
|
| 434 |
+
"total_time": 0,
|
| 435 |
+
"throughput": 0
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
self.model.eval()
|
| 439 |
+
start_time = time.time()
|
| 440 |
+
|
| 441 |
+
for i, example in enumerate(test_data[:max_samples]):
|
| 442 |
+
# Format prompt
|
| 443 |
+
user_content = example.get("instruction", "")
|
| 444 |
+
if example.get("input"):
|
| 445 |
+
user_content += f"\n\n{example['input']}"
|
| 446 |
+
|
| 447 |
+
messages = [{"role": "user", "content": user_content}]
|
| 448 |
+
|
| 449 |
+
try:
|
| 450 |
+
prompt = self.tokenizer.apply_chat_template(
|
| 451 |
+
messages,
|
| 452 |
+
tokenize=False,
|
| 453 |
+
add_generation_prompt=True
|
| 454 |
+
)
|
| 455 |
+
except Exception:
|
| 456 |
+
prompt = user_content
|
| 457 |
+
|
| 458 |
+
# Tokenize
|
| 459 |
+
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
|
| 460 |
+
|
| 461 |
+
# Generate
|
| 462 |
+
with torch.no_grad():
|
| 463 |
+
outputs = self.model.generate(
|
| 464 |
+
**inputs,
|
| 465 |
+
max_new_tokens=max_new_tokens,
|
| 466 |
+
temperature=0.7,
|
| 467 |
+
do_sample=True,
|
| 468 |
+
top_p=0.9
|
| 469 |
+
)
|
| 470 |
+
|
| 471 |
+
# Decode
|
| 472 |
+
response = self.tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
|
| 473 |
+
|
| 474 |
+
results["responses"].append({
|
| 475 |
+
"input": user_content,
|
| 476 |
+
"expected": example.get("output", ""),
|
| 477 |
+
"generated": response
|
| 478 |
+
})
|
| 479 |
+
|
| 480 |
+
# Calculate metrics
|
| 481 |
+
results["total_time"] = time.time() - start_time
|
| 482 |
+
results["avg_response_length"] = sum(len(r["generated"]) for r in results["responses"]) / len(results["responses"])
|
| 483 |
+
results["throughput"] = len(results["responses"]) / results["total_time"]
|
| 484 |
+
|
| 485 |
+
print(f"✅ Evaluation complete: {results['throughput']:.2f} examples/sec")
|
| 486 |
+
|
| 487 |
+
return results
|
fine_tuning/peft_trainer.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PEFT Trainer Module
|
| 3 |
+
|
| 4 |
+
General Parameter-Efficient Fine-Tuning trainer supporting multiple PEFT methods.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from typing import Optional, List, Dict, Any
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
import torch
|
| 10 |
+
from transformers import (
|
| 11 |
+
AutoModelForCausalLM,
|
| 12 |
+
AutoTokenizer,
|
| 13 |
+
TrainingArguments,
|
| 14 |
+
Trainer
|
| 15 |
+
)
|
| 16 |
+
from peft import (
|
| 17 |
+
get_peft_model,
|
| 18 |
+
PeftConfig,
|
| 19 |
+
PeftModel,
|
| 20 |
+
prepare_model_for_kbit_training
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class PEFTTrainer:
|
| 25 |
+
"""
|
| 26 |
+
General PEFT Trainer supporting multiple parameter-efficient fine-tuning methods.
|
| 27 |
+
|
| 28 |
+
Supports:
|
| 29 |
+
- LoRA (Low-Rank Adaptation)
|
| 30 |
+
- Prefix Tuning
|
| 31 |
+
- P-Tuning
|
| 32 |
+
- Prompt Tuning
|
| 33 |
+
- IA3 (Infused Adapter by Inhibiting and Amplifying Inner Activations)
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __init__(
|
| 37 |
+
self,
|
| 38 |
+
model_name: str,
|
| 39 |
+
peft_config: PeftConfig,
|
| 40 |
+
output_dir: str = "./models/peft_output"
|
| 41 |
+
):
|
| 42 |
+
"""
|
| 43 |
+
Initialize PEFT Trainer.
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
model_name: HuggingFace model path or name
|
| 47 |
+
peft_config: PEFT configuration (LoraConfig, PrefixTuningConfig, etc.)
|
| 48 |
+
output_dir: Directory for saving checkpoints and final model
|
| 49 |
+
"""
|
| 50 |
+
self.model_name = model_name
|
| 51 |
+
self.peft_config = peft_config
|
| 52 |
+
self.output_dir = Path(output_dir)
|
| 53 |
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
| 54 |
+
|
| 55 |
+
self.model = None
|
| 56 |
+
self.tokenizer = None
|
| 57 |
+
self.trainer = None
|
| 58 |
+
|
| 59 |
+
def load_model(
|
| 60 |
+
self,
|
| 61 |
+
use_4bit: bool = False,
|
| 62 |
+
use_8bit: bool = False,
|
| 63 |
+
device_map: str = "auto"
|
| 64 |
+
) -> None:
|
| 65 |
+
"""
|
| 66 |
+
Load model with PEFT configuration.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
use_4bit: Use 4-bit quantization
|
| 70 |
+
use_8bit: Use 8-bit quantization
|
| 71 |
+
device_map: Device mapping strategy
|
| 72 |
+
"""
|
| 73 |
+
print(f"Loading model: {self.model_name}")
|
| 74 |
+
|
| 75 |
+
# Load tokenizer
|
| 76 |
+
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 77 |
+
self.model_name,
|
| 78 |
+
trust_remote_code=True
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
if self.tokenizer.pad_token is None:
|
| 82 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 83 |
+
|
| 84 |
+
# Quantization config
|
| 85 |
+
quantization_config = None
|
| 86 |
+
if use_4bit or use_8bit:
|
| 87 |
+
from transformers import BitsAndBytesConfig
|
| 88 |
+
if use_4bit:
|
| 89 |
+
quantization_config = BitsAndBytesConfig(
|
| 90 |
+
load_in_4bit=True,
|
| 91 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 92 |
+
bnb_4bit_use_double_quant=True,
|
| 93 |
+
bnb_4bit_quant_type="nf4"
|
| 94 |
+
)
|
| 95 |
+
else:
|
| 96 |
+
quantization_config = BitsAndBytesConfig(load_in_8bit=True)
|
| 97 |
+
|
| 98 |
+
# Load base model
|
| 99 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 100 |
+
self.model_name,
|
| 101 |
+
quantization_config=quantization_config,
|
| 102 |
+
device_map=device_map,
|
| 103 |
+
trust_remote_code=True
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
# Prepare for k-bit training if quantized
|
| 107 |
+
if use_4bit or use_8bit:
|
| 108 |
+
self.model = prepare_model_for_kbit_training(self.model)
|
| 109 |
+
|
| 110 |
+
# Apply PEFT
|
| 111 |
+
self.model = get_peft_model(self.model, self.peft_config)
|
| 112 |
+
|
| 113 |
+
# Print trainable parameters
|
| 114 |
+
self.model.print_trainable_parameters()
|
| 115 |
+
|
| 116 |
+
print("✅ Model loaded with PEFT")
|
| 117 |
+
|
| 118 |
+
def save_model(self, save_path: Optional[str] = None) -> None:
|
| 119 |
+
"""
|
| 120 |
+
Save PEFT adapter weights.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
save_path: Path to save adapters
|
| 124 |
+
"""
|
| 125 |
+
if save_path is None:
|
| 126 |
+
save_path = str(self.output_dir / "final_model")
|
| 127 |
+
|
| 128 |
+
Path(save_path).mkdir(parents=True, exist_ok=True)
|
| 129 |
+
|
| 130 |
+
self.model.save_pretrained(save_path)
|
| 131 |
+
self.tokenizer.save_pretrained(save_path)
|
| 132 |
+
|
| 133 |
+
print(f"✅ PEFT model saved to: {save_path}")
|
| 134 |
+
|
| 135 |
+
def load_adapter(self, adapter_path: str) -> None:
|
| 136 |
+
"""
|
| 137 |
+
Load pre-trained PEFT adapter.
|
| 138 |
+
|
| 139 |
+
Args:
|
| 140 |
+
adapter_path: Path to adapter weights
|
| 141 |
+
"""
|
| 142 |
+
print(f"Loading PEFT adapter from: {adapter_path}")
|
| 143 |
+
|
| 144 |
+
self.model = PeftModel.from_pretrained(
|
| 145 |
+
self.model,
|
| 146 |
+
adapter_path
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
print("✅ Adapter loaded")
|
fine_tuning/training_loop.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Custom Training Loop Module
|
| 3 |
+
|
| 4 |
+
Provides custom training loop implementation with fine-grained control over training.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass, field
|
| 8 |
+
from typing import Optional, Dict, Any, Callable
|
| 9 |
+
import torch
|
| 10 |
+
from torch.utils.data import DataLoader
|
| 11 |
+
from tqdm import tqdm
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class TrainingConfig:
|
| 16 |
+
"""Configuration for custom training loop."""
|
| 17 |
+
num_epochs: int = 3
|
| 18 |
+
learning_rate: float = 2e-4
|
| 19 |
+
batch_size: int = 4
|
| 20 |
+
gradient_accumulation_steps: int = 4
|
| 21 |
+
max_grad_norm: float = 1.0
|
| 22 |
+
warmup_steps: int = 100
|
| 23 |
+
logging_steps: int = 10
|
| 24 |
+
eval_steps: int = 500
|
| 25 |
+
save_steps: int = 500
|
| 26 |
+
output_dir: str = "./models/output"
|
| 27 |
+
device: str = "cuda" if torch.cuda.is_available() else "cpu"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class TrainingLoop:
|
| 31 |
+
"""
|
| 32 |
+
Custom training loop for fine-grained control over the training process.
|
| 33 |
+
|
| 34 |
+
Provides manual control over:
|
| 35 |
+
- Forward/backward passes
|
| 36 |
+
- Gradient accumulation
|
| 37 |
+
- Learning rate scheduling
|
| 38 |
+
- Logging and evaluation
|
| 39 |
+
- Checkpointing
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
model: torch.nn.Module,
|
| 45 |
+
train_dataloader: DataLoader,
|
| 46 |
+
eval_dataloader: Optional[DataLoader] = None,
|
| 47 |
+
config: Optional[TrainingConfig] = None
|
| 48 |
+
):
|
| 49 |
+
"""
|
| 50 |
+
Initialize custom training loop.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
model: PyTorch model to train
|
| 54 |
+
train_dataloader: Training data loader
|
| 55 |
+
eval_dataloader: Optional evaluation data loader
|
| 56 |
+
config: Training configuration
|
| 57 |
+
"""
|
| 58 |
+
self.model = model
|
| 59 |
+
self.train_dataloader = train_dataloader
|
| 60 |
+
self.eval_dataloader = eval_dataloader
|
| 61 |
+
self.config = config or TrainingConfig()
|
| 62 |
+
|
| 63 |
+
self.optimizer = None
|
| 64 |
+
self.scheduler = None
|
| 65 |
+
self.global_step = 0
|
| 66 |
+
self.current_epoch = 0
|
| 67 |
+
|
| 68 |
+
def setup_optimizer(self, optimizer_class=torch.optim.AdamW, **optimizer_kwargs):
|
| 69 |
+
"""
|
| 70 |
+
Setup optimizer and learning rate scheduler.
|
| 71 |
+
|
| 72 |
+
Args:
|
| 73 |
+
optimizer_class: Optimizer class to use
|
| 74 |
+
**optimizer_kwargs: Additional optimizer arguments
|
| 75 |
+
"""
|
| 76 |
+
self.optimizer = optimizer_class(
|
| 77 |
+
self.model.parameters(),
|
| 78 |
+
lr=self.config.learning_rate,
|
| 79 |
+
**optimizer_kwargs
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# Linear warmup scheduler
|
| 83 |
+
def lr_lambda(current_step: int):
|
| 84 |
+
if current_step < self.config.warmup_steps:
|
| 85 |
+
return float(current_step) / float(max(1, self.config.warmup_steps))
|
| 86 |
+
return 1.0
|
| 87 |
+
|
| 88 |
+
self.scheduler = torch.optim.lr_scheduler.LambdaLR(
|
| 89 |
+
self.optimizer,
|
| 90 |
+
lr_lambda
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
def train_step(self, batch: Dict[str, torch.Tensor]) -> float:
|
| 94 |
+
"""
|
| 95 |
+
Perform a single training step.
|
| 96 |
+
|
| 97 |
+
Args:
|
| 98 |
+
batch: Batch of training data
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
Loss value
|
| 102 |
+
"""
|
| 103 |
+
# Move batch to device
|
| 104 |
+
batch = {k: v.to(self.config.device) for k, v in batch.items()}
|
| 105 |
+
|
| 106 |
+
# Forward pass
|
| 107 |
+
outputs = self.model(**batch)
|
| 108 |
+
loss = outputs.loss
|
| 109 |
+
|
| 110 |
+
# Scale loss for gradient accumulation
|
| 111 |
+
loss = loss / self.config.gradient_accumulation_steps
|
| 112 |
+
|
| 113 |
+
# Backward pass
|
| 114 |
+
loss.backward()
|
| 115 |
+
|
| 116 |
+
return loss.item()
|
| 117 |
+
|
| 118 |
+
def train_epoch(self) -> Dict[str, float]:
|
| 119 |
+
"""
|
| 120 |
+
Train for one epoch.
|
| 121 |
+
|
| 122 |
+
Returns:
|
| 123 |
+
Training metrics
|
| 124 |
+
"""
|
| 125 |
+
self.model.train()
|
| 126 |
+
total_loss = 0
|
| 127 |
+
num_batches = 0
|
| 128 |
+
|
| 129 |
+
progress_bar = tqdm(
|
| 130 |
+
self.train_dataloader,
|
| 131 |
+
desc=f"Epoch {self.current_epoch + 1}/{self.config.num_epochs}"
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
for step, batch in enumerate(progress_bar):
|
| 135 |
+
# Training step
|
| 136 |
+
loss = self.train_step(batch)
|
| 137 |
+
total_loss += loss
|
| 138 |
+
|
| 139 |
+
# Gradient accumulation
|
| 140 |
+
if (step + 1) % self.config.gradient_accumulation_steps == 0:
|
| 141 |
+
# Clip gradients
|
| 142 |
+
torch.nn.utils.clip_grad_norm_(
|
| 143 |
+
self.model.parameters(),
|
| 144 |
+
self.config.max_grad_norm
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# Optimizer step
|
| 148 |
+
self.optimizer.step()
|
| 149 |
+
self.scheduler.step()
|
| 150 |
+
self.optimizer.zero_grad()
|
| 151 |
+
|
| 152 |
+
self.global_step += 1
|
| 153 |
+
num_batches += 1
|
| 154 |
+
|
| 155 |
+
# Update progress bar
|
| 156 |
+
progress_bar.set_postfix({
|
| 157 |
+
"loss": total_loss / num_batches,
|
| 158 |
+
"lr": self.scheduler.get_last_lr()[0]
|
| 159 |
+
})
|
| 160 |
+
|
| 161 |
+
# Logging
|
| 162 |
+
if self.global_step % self.config.logging_steps == 0:
|
| 163 |
+
avg_loss = total_loss / num_batches
|
| 164 |
+
print(f"Step {self.global_step}: loss={avg_loss:.4f}")
|
| 165 |
+
|
| 166 |
+
# Evaluation
|
| 167 |
+
if self.eval_dataloader and self.global_step % self.config.eval_steps == 0:
|
| 168 |
+
eval_metrics = self.evaluate()
|
| 169 |
+
print(f"Evaluation: {eval_metrics}")
|
| 170 |
+
self.model.train()
|
| 171 |
+
|
| 172 |
+
return {
|
| 173 |
+
"loss": total_loss / max(num_batches, 1),
|
| 174 |
+
"epoch": self.current_epoch
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
def evaluate(self) -> Dict[str, float]:
|
| 178 |
+
"""
|
| 179 |
+
Evaluate model on validation set.
|
| 180 |
+
|
| 181 |
+
Returns:
|
| 182 |
+
Evaluation metrics
|
| 183 |
+
"""
|
| 184 |
+
if self.eval_dataloader is None:
|
| 185 |
+
return {}
|
| 186 |
+
|
| 187 |
+
self.model.eval()
|
| 188 |
+
total_loss = 0
|
| 189 |
+
num_batches = 0
|
| 190 |
+
|
| 191 |
+
with torch.no_grad():
|
| 192 |
+
for batch in tqdm(self.eval_dataloader, desc="Evaluating"):
|
| 193 |
+
batch = {k: v.to(self.config.device) for k, v in batch.items()}
|
| 194 |
+
outputs = self.model(**batch)
|
| 195 |
+
total_loss += outputs.loss.item()
|
| 196 |
+
num_batches += 1
|
| 197 |
+
|
| 198 |
+
return {
|
| 199 |
+
"eval_loss": total_loss / max(num_batches, 1)
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
def train(self, callback: Optional[Callable] = None) -> Dict[str, Any]:
|
| 203 |
+
"""
|
| 204 |
+
Run full training loop.
|
| 205 |
+
|
| 206 |
+
Args:
|
| 207 |
+
callback: Optional callback function called after each epoch
|
| 208 |
+
|
| 209 |
+
Returns:
|
| 210 |
+
Training history
|
| 211 |
+
"""
|
| 212 |
+
if self.optimizer is None:
|
| 213 |
+
self.setup_optimizer()
|
| 214 |
+
|
| 215 |
+
print(f"Starting training for {self.config.num_epochs} epochs")
|
| 216 |
+
print(f"Device: {self.config.device}")
|
| 217 |
+
print(f"Batch size: {self.config.batch_size}")
|
| 218 |
+
print(f"Gradient accumulation: {self.config.gradient_accumulation_steps}")
|
| 219 |
+
|
| 220 |
+
history = {
|
| 221 |
+
"train_loss": [],
|
| 222 |
+
"eval_loss": []
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
for epoch in range(self.config.num_epochs):
|
| 226 |
+
self.current_epoch = epoch
|
| 227 |
+
|
| 228 |
+
# Train epoch
|
| 229 |
+
train_metrics = self.train_epoch()
|
| 230 |
+
history["train_loss"].append(train_metrics["loss"])
|
| 231 |
+
|
| 232 |
+
# Evaluate
|
| 233 |
+
if self.eval_dataloader:
|
| 234 |
+
eval_metrics = self.evaluate()
|
| 235 |
+
history["eval_loss"].append(eval_metrics.get("eval_loss", 0))
|
| 236 |
+
|
| 237 |
+
# Callback
|
| 238 |
+
if callback:
|
| 239 |
+
callback(epoch, train_metrics)
|
| 240 |
+
|
| 241 |
+
print("✅ Training complete!")
|
| 242 |
+
return history
|
| 243 |
+
|
| 244 |
+
def save_checkpoint(self, path: str) -> None:
|
| 245 |
+
"""
|
| 246 |
+
Save training checkpoint.
|
| 247 |
+
|
| 248 |
+
Args:
|
| 249 |
+
path: Path to save checkpoint
|
| 250 |
+
"""
|
| 251 |
+
checkpoint = {
|
| 252 |
+
"model_state_dict": self.model.state_dict(),
|
| 253 |
+
"optimizer_state_dict": self.optimizer.state_dict(),
|
| 254 |
+
"scheduler_state_dict": self.scheduler.state_dict(),
|
| 255 |
+
"global_step": self.global_step,
|
| 256 |
+
"epoch": self.current_epoch
|
| 257 |
+
}
|
| 258 |
+
torch.save(checkpoint, path)
|
| 259 |
+
print(f"Checkpoint saved to: {path}")
|
| 260 |
+
|
| 261 |
+
def load_checkpoint(self, path: str) -> None:
|
| 262 |
+
"""
|
| 263 |
+
Load training checkpoint.
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
path: Path to checkpoint
|
| 267 |
+
"""
|
| 268 |
+
checkpoint = torch.load(path)
|
| 269 |
+
self.model.load_state_dict(checkpoint["model_state_dict"])
|
| 270 |
+
self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
|
| 271 |
+
self.scheduler.load_state_dict(checkpoint["scheduler_state_dict"])
|
| 272 |
+
self.global_step = checkpoint["global_step"]
|
| 273 |
+
self.current_epoch = checkpoint["epoch"]
|
| 274 |
+
print(f"Checkpoint loaded from: {path}")
|
model_registry.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Model Registry - Centralized model configuration and management
|
| 3 |
+
|
| 4 |
+
Provides pre-configured models with their specs, LoRA settings, and quantization recommendations.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from typing import Dict, List, Optional
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class ModelInfo:
|
| 13 |
+
"""Information about a model in the registry"""
|
| 14 |
+
name: str
|
| 15 |
+
model_id: str # HuggingFace model ID
|
| 16 |
+
description: str
|
| 17 |
+
vram_required_gb: int
|
| 18 |
+
recommended_lora_rank: int
|
| 19 |
+
recommended_quantization: str
|
| 20 |
+
model_type: str # "local" or "cloud"
|
| 21 |
+
ollama_equivalent: Optional[str] = None
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class ModelRegistry:
|
| 25 |
+
"""Registry of pre-configured models for fine-tuning"""
|
| 26 |
+
|
| 27 |
+
def __init__(self):
|
| 28 |
+
self.models: Dict[str, ModelInfo] = {}
|
| 29 |
+
self._register_default_models()
|
| 30 |
+
|
| 31 |
+
def _register_default_models(self):
|
| 32 |
+
"""Register default models with their configurations"""
|
| 33 |
+
|
| 34 |
+
# Qwen Models
|
| 35 |
+
self.models["qwen2.5-7b"] = ModelInfo(
|
| 36 |
+
name="Qwen 2.5 7B Instruct",
|
| 37 |
+
model_id="Qwen/Qwen2.5-7B-Instruct",
|
| 38 |
+
description="Fast 7B parameter model, good for quick testing",
|
| 39 |
+
vram_required_gb=6,
|
| 40 |
+
recommended_lora_rank=16,
|
| 41 |
+
recommended_quantization="4bit",
|
| 42 |
+
model_type="cloud",
|
| 43 |
+
ollama_equivalent="qwen2.5:7b"
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
self.models["qwen2.5-32b"] = ModelInfo(
|
| 47 |
+
name="Qwen 2.5 32B Instruct",
|
| 48 |
+
model_id="Qwen/Qwen2.5-32B-Instruct",
|
| 49 |
+
description="High-quality 32B model for production use",
|
| 50 |
+
vram_required_gb=24,
|
| 51 |
+
recommended_lora_rank=32,
|
| 52 |
+
recommended_quantization="4bit",
|
| 53 |
+
model_type="cloud",
|
| 54 |
+
ollama_equivalent="qwen2.5:32b"
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
self.models["qwen2.5-72b"] = ModelInfo(
|
| 58 |
+
name="Qwen 2.5 72B Instruct",
|
| 59 |
+
model_id="Qwen/Qwen2.5-72B-Instruct",
|
| 60 |
+
description="Largest Qwen 2.5 model for maximum performance",
|
| 61 |
+
vram_required_gb=48,
|
| 62 |
+
recommended_lora_rank=64,
|
| 63 |
+
recommended_quantization="4bit",
|
| 64 |
+
model_type="cloud",
|
| 65 |
+
ollama_equivalent="qwen2.5:72b"
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Llama Models
|
| 69 |
+
self.models["llama-3.1-8b"] = ModelInfo(
|
| 70 |
+
name="Llama 3.1 8B Instruct",
|
| 71 |
+
model_id="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
| 72 |
+
description="Meta's Llama 3.1 8B model",
|
| 73 |
+
vram_required_gb=8,
|
| 74 |
+
recommended_lora_rank=16,
|
| 75 |
+
recommended_quantization="4bit",
|
| 76 |
+
model_type="cloud",
|
| 77 |
+
ollama_equivalent="llama3.1:8b"
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
self.models["llama-3.1-70b"] = ModelInfo(
|
| 81 |
+
name="Llama 3.1 70B Instruct",
|
| 82 |
+
model_id="meta-llama/Meta-Llama-3.1-70B-Instruct",
|
| 83 |
+
description="Large Llama model for maximum performance",
|
| 84 |
+
vram_required_gb=48,
|
| 85 |
+
recommended_lora_rank=64,
|
| 86 |
+
recommended_quantization="4bit",
|
| 87 |
+
model_type="cloud",
|
| 88 |
+
ollama_equivalent="llama3.1:70b"
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
# Mistral Models
|
| 92 |
+
self.models["mistral-7b"] = ModelInfo(
|
| 93 |
+
name="Mistral 7B Instruct v0.3",
|
| 94 |
+
model_id="mistralai/Mistral-7B-Instruct-v0.3",
|
| 95 |
+
description="Efficient 7B model from Mistral AI",
|
| 96 |
+
vram_required_gb=6,
|
| 97 |
+
recommended_lora_rank=16,
|
| 98 |
+
recommended_quantization="4bit",
|
| 99 |
+
model_type="cloud",
|
| 100 |
+
ollama_equivalent="mistral:7b"
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# Mixtral (MoE)
|
| 104 |
+
self.models["mixtral-8x7b"] = ModelInfo(
|
| 105 |
+
name="Mixtral 8x7B Instruct",
|
| 106 |
+
model_id="mistralai/Mixtral-8x7B-Instruct-v0.1",
|
| 107 |
+
description="Mixture-of-Experts model with 8x7B parameters",
|
| 108 |
+
vram_required_gb=40,
|
| 109 |
+
recommended_lora_rank=32,
|
| 110 |
+
recommended_quantization="4bit",
|
| 111 |
+
model_type="cloud",
|
| 112 |
+
ollama_equivalent="mixtral:8x7b"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
self.models["mixtral-8x22b"] = ModelInfo(
|
| 116 |
+
name="Mixtral 8x22B Instruct",
|
| 117 |
+
model_id="mistralai/Mixtral-8x22B-Instruct-v0.1",
|
| 118 |
+
description="Large MoE model for highest quality",
|
| 119 |
+
vram_required_gb=80,
|
| 120 |
+
recommended_lora_rank=64,
|
| 121 |
+
recommended_quantization="4bit",
|
| 122 |
+
model_type="cloud",
|
| 123 |
+
ollama_equivalent=None
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
# Phi Models (Microsoft)
|
| 127 |
+
self.models["phi-3-mini"] = ModelInfo(
|
| 128 |
+
name="Phi-3 Mini 3.8B",
|
| 129 |
+
model_id="microsoft/Phi-3-mini-4k-instruct",
|
| 130 |
+
description="Small efficient model for quick testing",
|
| 131 |
+
vram_required_gb=4,
|
| 132 |
+
recommended_lora_rank=8,
|
| 133 |
+
recommended_quantization="4bit",
|
| 134 |
+
model_type="cloud",
|
| 135 |
+
ollama_equivalent="phi3:mini"
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
# Gemma Models (Google)
|
| 139 |
+
self.models["gemma-7b"] = ModelInfo(
|
| 140 |
+
name="Gemma 7B Instruct",
|
| 141 |
+
model_id="google/gemma-7b-it",
|
| 142 |
+
description="Google's Gemma 7B instruction-tuned model",
|
| 143 |
+
vram_required_gb=6,
|
| 144 |
+
recommended_lora_rank=16,
|
| 145 |
+
recommended_quantization="4bit",
|
| 146 |
+
model_type="cloud",
|
| 147 |
+
ollama_equivalent="gemma:7b"
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
def get_model(self, model_id: str) -> Optional[ModelInfo]:
|
| 151 |
+
"""Get model info by ID"""
|
| 152 |
+
return self.models.get(model_id)
|
| 153 |
+
|
| 154 |
+
def get_all_models(self) -> Dict[str, ModelInfo]:
|
| 155 |
+
"""Get all registered models"""
|
| 156 |
+
return self.models
|
| 157 |
+
|
| 158 |
+
def register_custom_model(self, model_id: str, info: ModelInfo):
|
| 159 |
+
"""Register a custom model"""
|
| 160 |
+
self.models[model_id] = info
|
| 161 |
+
|
| 162 |
+
def get_models_by_vram(self, max_vram_gb: int) -> List[ModelInfo]:
|
| 163 |
+
"""Get models that fit within VRAM budget"""
|
| 164 |
+
return [
|
| 165 |
+
info for info in self.models.values()
|
| 166 |
+
if info.vram_required_gb <= max_vram_gb
|
| 167 |
+
]
|
| 168 |
+
|
| 169 |
+
def get_model_choices_for_gui(self) -> List[str]:
|
| 170 |
+
"""Get list of model choices formatted for GUI dropdown"""
|
| 171 |
+
choices = []
|
| 172 |
+
for model_id, info in self.models.items():
|
| 173 |
+
label = f"{info.name} ({info.vram_required_gb}GB VRAM)"
|
| 174 |
+
choices.append((label, model_id))
|
| 175 |
+
return choices
|
| 176 |
+
|
| 177 |
+
def get_model_names(self) -> List[str]:
|
| 178 |
+
"""Get list of model names"""
|
| 179 |
+
return [info.name for info in self.models.values()]
|
| 180 |
+
|
| 181 |
+
def get_model_ids(self) -> List[str]:
|
| 182 |
+
"""Get list of model IDs"""
|
| 183 |
+
return list(self.models.keys())
|
| 184 |
+
|
| 185 |
+
def list_models(self) -> List[str]:
|
| 186 |
+
"""Alias for get_model_ids() - returns list of model IDs"""
|
| 187 |
+
return self.get_model_ids()
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# Global registry instance
|
| 191 |
+
_registry = None
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def get_registry() -> ModelRegistry:
|
| 195 |
+
"""Get the global model registry instance"""
|
| 196 |
+
global _registry
|
| 197 |
+
if _registry is None:
|
| 198 |
+
_registry = ModelRegistry()
|
| 199 |
+
return _registry
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
# Convenience function
|
| 203 |
+
def get_model_info(model_id: str) -> Optional[ModelInfo]:
|
| 204 |
+
"""Get model info by ID from global registry"""
|
| 205 |
+
return get_registry().get_model(model_id)
|
ollama_integration.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Ollama Integration - Local model serving and HuggingFace mapping
|
| 3 |
+
|
| 4 |
+
Provides integration with Ollama for local model inference and maps
|
| 5 |
+
Ollama models to their HuggingFace equivalents for training.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import requests
|
| 9 |
+
from typing import Optional, Dict, List
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class OllamaClient:
|
| 13 |
+
"""Client for interacting with Ollama API"""
|
| 14 |
+
|
| 15 |
+
def __init__(self, base_url: str = "http://localhost:11434"):
|
| 16 |
+
self.base_url = base_url
|
| 17 |
+
|
| 18 |
+
def generate(self, model: str, prompt: str, **kwargs) -> Dict:
|
| 19 |
+
"""Generate text using Ollama model"""
|
| 20 |
+
url = f"{self.base_url}/api/generate"
|
| 21 |
+
|
| 22 |
+
payload = {
|
| 23 |
+
"model": model,
|
| 24 |
+
"prompt": prompt,
|
| 25 |
+
"stream": False,
|
| 26 |
+
**kwargs
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
response = requests.post(url, json=payload, timeout=120)
|
| 31 |
+
response.raise_for_status()
|
| 32 |
+
return response.json()
|
| 33 |
+
except requests.exceptions.RequestException as e:
|
| 34 |
+
return {"error": str(e), "response": ""}
|
| 35 |
+
|
| 36 |
+
def chat(self, model: str, messages: List[Dict], **kwargs) -> Dict:
|
| 37 |
+
"""Chat with Ollama model"""
|
| 38 |
+
url = f"{self.base_url}/api/chat"
|
| 39 |
+
|
| 40 |
+
payload = {
|
| 41 |
+
"model": model,
|
| 42 |
+
"messages": messages,
|
| 43 |
+
"stream": False,
|
| 44 |
+
**kwargs
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
try:
|
| 48 |
+
response = requests.post(url, json=payload, timeout=120)
|
| 49 |
+
response.raise_for_status()
|
| 50 |
+
return response.json()
|
| 51 |
+
except requests.exceptions.RequestException as e:
|
| 52 |
+
return {"error": str(e), "message": {"content": ""}}
|
| 53 |
+
|
| 54 |
+
def list_models(self) -> List[str]:
|
| 55 |
+
"""List available Ollama models"""
|
| 56 |
+
url = f"{self.base_url}/api/tags"
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
response = requests.get(url, timeout=10)
|
| 60 |
+
response.raise_for_status()
|
| 61 |
+
data = response.json()
|
| 62 |
+
return [model["name"] for model in data.get("models", [])]
|
| 63 |
+
except requests.exceptions.RequestException:
|
| 64 |
+
return []
|
| 65 |
+
|
| 66 |
+
def is_available(self) -> bool:
|
| 67 |
+
"""Check if Ollama is running"""
|
| 68 |
+
try:
|
| 69 |
+
response = requests.get(self.base_url, timeout=5)
|
| 70 |
+
return response.status_code == 200
|
| 71 |
+
except requests.exceptions.RequestException:
|
| 72 |
+
return False
|
| 73 |
+
|
| 74 |
+
def model_exists(self, model: str) -> bool:
|
| 75 |
+
"""Check if a specific model is available"""
|
| 76 |
+
return model in self.list_models()
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# Mapping of Ollama models to HuggingFace equivalents
|
| 80 |
+
OLLAMA_TO_HF_MAP = {
|
| 81 |
+
"qwen2.5:7b": "Qwen/Qwen2.5-7B-Instruct",
|
| 82 |
+
"qwen2.5:32b": "Qwen/Qwen2.5-32B-Instruct",
|
| 83 |
+
"llama3.1:8b": "meta-llama/Meta-Llama-3.1-8B-Instruct",
|
| 84 |
+
"llama3.1:70b": "meta-llama/Meta-Llama-3.1-70B-Instruct",
|
| 85 |
+
"mistral:7b": "mistralai/Mistral-7B-Instruct-v0.3",
|
| 86 |
+
"mixtral:8x7b": "mistralai/Mixtral-8x7B-Instruct-v0.1",
|
| 87 |
+
"phi3:mini": "microsoft/Phi-3-mini-4k-instruct",
|
| 88 |
+
"gemma:7b": "google/gemma-7b-it",
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def get_hf_model_for_ollama(ollama_model: str) -> Optional[str]:
|
| 93 |
+
"""Get the HuggingFace model ID for an Ollama model"""
|
| 94 |
+
# Handle versioned models (e.g., "qwen2.5:7b-instruct" -> "qwen2.5:7b")
|
| 95 |
+
base_model = ollama_model.split("-")[0] if "-" in ollama_model else ollama_model
|
| 96 |
+
|
| 97 |
+
return OLLAMA_TO_HF_MAP.get(base_model)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def get_ollama_for_hf(hf_model: str) -> Optional[str]:
|
| 101 |
+
"""Get the Ollama model for a HuggingFace model ID"""
|
| 102 |
+
reverse_map = {v: k for k, v in OLLAMA_TO_HF_MAP.items()}
|
| 103 |
+
return reverse_map.get(hf_model)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def test_financial_advisor_ollama(
|
| 107 |
+
model: str = "qwen2.5:7b",
|
| 108 |
+
ollama_client: Optional[OllamaClient] = None
|
| 109 |
+
) -> str:
|
| 110 |
+
"""Test a financial advisor model via Ollama"""
|
| 111 |
+
|
| 112 |
+
if ollama_client is None:
|
| 113 |
+
ollama_client = OllamaClient()
|
| 114 |
+
|
| 115 |
+
if not ollama_client.is_available():
|
| 116 |
+
return "Error: Ollama is not running. Please start Ollama first."
|
| 117 |
+
|
| 118 |
+
if not ollama_client.model_exists(model):
|
| 119 |
+
return f"Error: Model '{model}' is not available. Please pull it first with: ollama pull {model}"
|
| 120 |
+
|
| 121 |
+
# Test prompt
|
| 122 |
+
prompt = """You are a financial advisor. A client asks:
|
| 123 |
+
|
| 124 |
+
"I'm 35 years old with $50,000 in savings. Should I invest in stocks or bonds?"
|
| 125 |
+
|
| 126 |
+
Provide professional financial advice."""
|
| 127 |
+
|
| 128 |
+
result = ollama_client.generate(model, prompt)
|
| 129 |
+
|
| 130 |
+
if "error" in result:
|
| 131 |
+
return f"Error: {result['error']}"
|
| 132 |
+
|
| 133 |
+
return result.get("response", "No response")
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# Global client instance
|
| 137 |
+
_client = None
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def get_ollama_client() -> OllamaClient:
|
| 141 |
+
"""Get the global Ollama client instance"""
|
| 142 |
+
global _client
|
| 143 |
+
if _client is None:
|
| 144 |
+
_client = OllamaClient()
|
| 145 |
+
return _client
|
requirements.txt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GUI Requirements
|
| 2 |
+
# Install with: pip install -r requirements_gui.txt
|
| 3 |
+
|
| 4 |
+
# Core training requirements
|
| 5 |
+
torch>=2.0.0
|
| 6 |
+
transformers>=4.46.0
|
| 7 |
+
peft>=0.4.0
|
| 8 |
+
accelerate>=0.20.0
|
| 9 |
+
bitsandbytes>=0.41.0
|
| 10 |
+
datasets>=2.12.0
|
| 11 |
+
trl>=0.12.0
|
| 12 |
+
sentencepiece>=0.1.99
|
| 13 |
+
protobuf>=3.20.0
|
| 14 |
+
|
| 15 |
+
# GUI framework
|
| 16 |
+
gradio>=4.0.0
|
| 17 |
+
|
| 18 |
+
# Security for API key storage
|
| 19 |
+
cryptography>=41.0.0
|
| 20 |
+
|
| 21 |
+
# Configuration
|
| 22 |
+
pyyaml>=6.0
|
| 23 |
+
python-dotenv>=1.0.0
|
| 24 |
+
|
| 25 |
+
# Utilities
|
| 26 |
+
tqdm>=4.65.0
|
| 27 |
+
numpy>=1.24.0
|
| 28 |
+
|
| 29 |
+
# API integrations for synthetic data generation
|
| 30 |
+
openai>=1.0.0
|
| 31 |
+
anthropic>=0.8.0
|
| 32 |
+
|
| 33 |
+
# Ollama integration
|
| 34 |
+
requests>=2.31.0
|
| 35 |
+
|
| 36 |
+
# RunPod cloud integration
|
| 37 |
+
paramiko>=3.0.0
|
| 38 |
+
scp>=0.14.0
|
secure_config.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Secure Configuration - Encrypted API key storage using Fernet encryption
|
| 3 |
+
|
| 4 |
+
Provides secure storage and retrieval of API keys with encryption at rest.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from typing import Dict, Optional
|
| 11 |
+
from cryptography.fernet import Fernet
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class SecureConfig:
|
| 15 |
+
"""Manages encrypted API key storage"""
|
| 16 |
+
|
| 17 |
+
def __init__(self, secrets_dir: str = ".secrets"):
|
| 18 |
+
self.secrets_dir = Path(secrets_dir)
|
| 19 |
+
self.secrets_dir.mkdir(exist_ok=True)
|
| 20 |
+
|
| 21 |
+
self.key_file = self.secrets_dir / "encryption.key"
|
| 22 |
+
self.data_file = self.secrets_dir / "api_keys.enc"
|
| 23 |
+
|
| 24 |
+
self.fernet = self._load_or_create_key()
|
| 25 |
+
|
| 26 |
+
def _load_or_create_key(self) -> Fernet:
|
| 27 |
+
"""Load existing encryption key or create new one"""
|
| 28 |
+
if self.key_file.exists():
|
| 29 |
+
with open(self.key_file, "rb") as f:
|
| 30 |
+
key = f.read()
|
| 31 |
+
else:
|
| 32 |
+
key = Fernet.generate_key()
|
| 33 |
+
with open(self.key_file, "wb") as f:
|
| 34 |
+
f.write(key)
|
| 35 |
+
|
| 36 |
+
return Fernet(key)
|
| 37 |
+
|
| 38 |
+
def save_keys(self, api_keys: Dict[str, str]):
|
| 39 |
+
"""Save API keys with encryption"""
|
| 40 |
+
# Remove empty keys
|
| 41 |
+
api_keys = {k: v for k, v in api_keys.items() if v and v.strip()}
|
| 42 |
+
|
| 43 |
+
# Serialize to JSON
|
| 44 |
+
json_data = json.dumps(api_keys)
|
| 45 |
+
|
| 46 |
+
# Encrypt
|
| 47 |
+
encrypted_data = self.fernet.encrypt(json_data.encode())
|
| 48 |
+
|
| 49 |
+
# Save to file
|
| 50 |
+
with open(self.data_file, "wb") as f:
|
| 51 |
+
f.write(encrypted_data)
|
| 52 |
+
|
| 53 |
+
# Also set environment variables for current session
|
| 54 |
+
for key, value in api_keys.items():
|
| 55 |
+
os.environ[key] = value
|
| 56 |
+
|
| 57 |
+
def load_keys(self) -> Dict[str, str]:
|
| 58 |
+
"""Load and decrypt API keys"""
|
| 59 |
+
if not self.data_file.exists():
|
| 60 |
+
return {}
|
| 61 |
+
|
| 62 |
+
try:
|
| 63 |
+
# Read encrypted data
|
| 64 |
+
with open(self.data_file, "rb") as f:
|
| 65 |
+
encrypted_data = f.read()
|
| 66 |
+
|
| 67 |
+
# Decrypt
|
| 68 |
+
decrypted_data = self.fernet.decrypt(encrypted_data)
|
| 69 |
+
|
| 70 |
+
# Deserialize
|
| 71 |
+
api_keys = json.loads(decrypted_data.decode())
|
| 72 |
+
|
| 73 |
+
# Set environment variables
|
| 74 |
+
for key, value in api_keys.items():
|
| 75 |
+
os.environ[key] = value
|
| 76 |
+
|
| 77 |
+
return api_keys
|
| 78 |
+
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"Error loading keys: {e}")
|
| 81 |
+
return {}
|
| 82 |
+
|
| 83 |
+
def get_key(self, key_name: str) -> Optional[str]:
|
| 84 |
+
"""Get a specific API key"""
|
| 85 |
+
# First try environment variable
|
| 86 |
+
value = os.environ.get(key_name)
|
| 87 |
+
if value:
|
| 88 |
+
return value
|
| 89 |
+
|
| 90 |
+
# Then try loading from encrypted file
|
| 91 |
+
keys = self.load_keys()
|
| 92 |
+
return keys.get(key_name)
|
| 93 |
+
|
| 94 |
+
def get_masked_key(self, key_name: str) -> Optional[str]:
|
| 95 |
+
"""Get a masked version of an API key for display"""
|
| 96 |
+
key = self.get_key(key_name)
|
| 97 |
+
if not key:
|
| 98 |
+
return None
|
| 99 |
+
|
| 100 |
+
# Show first 8 and last 4 characters
|
| 101 |
+
if len(key) > 20:
|
| 102 |
+
return f"{key[:8]}...{key[-4:]}"
|
| 103 |
+
else:
|
| 104 |
+
return f"{key[:4]}...{key[-2:]}"
|
| 105 |
+
|
| 106 |
+
def delete_keys(self):
|
| 107 |
+
"""Delete all stored keys"""
|
| 108 |
+
if self.data_file.exists():
|
| 109 |
+
self.data_file.unlink()
|
| 110 |
+
|
| 111 |
+
# Also clear environment variables
|
| 112 |
+
env_keys = [
|
| 113 |
+
"HUGGINGFACE_TOKEN",
|
| 114 |
+
"OPENAI_API_KEY",
|
| 115 |
+
"ANTHROPIC_API_KEY",
|
| 116 |
+
"WANDB_API_KEY",
|
| 117 |
+
"ELEVEN_API_KEY",
|
| 118 |
+
"RUNPOD_API_KEY"
|
| 119 |
+
]
|
| 120 |
+
for key in env_keys:
|
| 121 |
+
os.environ.pop(key, None)
|
| 122 |
+
|
| 123 |
+
def has_key(self, key_name: str) -> bool:
|
| 124 |
+
"""Check if a key exists"""
|
| 125 |
+
return self.get_key(key_name) is not None
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
# Global instance
|
| 129 |
+
_config = None
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def get_config() -> SecureConfig:
|
| 133 |
+
"""Get the global secure config instance"""
|
| 134 |
+
global _config
|
| 135 |
+
if _config is None:
|
| 136 |
+
_config = SecureConfig()
|
| 137 |
+
return _config
|