Spaces:
Sleeping
🎯 Add Automatic Sample Data System for New Users
Browse files✅ Features implemented:
• Automatic sample data insertion for empty databases
• 3 curated examples showing increasing complexity levels
• Pre-generated knowledge graphs for each sample trace
• Non-destructive insertion (skips if data exists)
📊 Sample data includes:
• Basic Q&A: Simple arithmetic calculation (6 entities, 5 relations)
• Research Task: Location/hours research (6 entities, 6 relations)
• Multi-Agent: Algorithm analysis (10 entities, 16 relations)
🔧 Technical implementation:
• backend/database/sample_data.py - Core data and insertion logic
• backend/database/init_db.py - Modified to trigger insertion
• Full transaction support with error handling
• Comprehensive logging and status reporting
🎯 User experience:
• New users see working examples immediately
• Examples serve as templates and learning materials
• Demonstrates different trace types and complexity levels
• No more empty system on first launch
📚 Documentation:
• Complete README with usage examples
• Troubleshooting guide
• Maintenance instructions
- agentgraph/extraction/graph_processing/knowledge_graph_processor.py +1 -2
- agentgraph/extraction/graph_utilities/knowledge_graph_merger.py +1 -2
- agentgraph/methods/production/multi_agent_knowledge_extractor.py +1 -2
- agentgraph/testing/knowledge_graph_tester.py +1 -2
- backend/app.py +0 -54
- backend/database/README_sample_data.md +147 -0
- backend/database/init_db.py +26 -0
- backend/database/sample_data.py +624 -0
- backend/scripts/preload_sample_data.py +0 -395
|
@@ -66,8 +66,7 @@ from agentgraph.reconstruction.content_reference_resolver import ContentReferenc
|
|
| 66 |
|
| 67 |
# Load OpenAI API key from configuration
|
| 68 |
from utils.config import OPENAI_API_KEY
|
| 69 |
-
|
| 70 |
-
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
| 71 |
|
| 72 |
|
| 73 |
class SlidingWindowMonitor:
|
|
|
|
| 66 |
|
| 67 |
# Load OpenAI API key from configuration
|
| 68 |
from utils.config import OPENAI_API_KEY
|
| 69 |
+
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
|
|
|
| 70 |
|
| 71 |
|
| 72 |
class SlidingWindowMonitor:
|
|
@@ -50,8 +50,7 @@ from agentgraph.shared.models.reference_based import KnowledgeGraph
|
|
| 50 |
|
| 51 |
# Load OpenAI API key from configuration
|
| 52 |
from utils.config import OPENAI_API_KEY
|
| 53 |
-
|
| 54 |
-
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
| 55 |
# Note: OPENAI_MODEL_NAME will be set dynamically in __init__ method
|
| 56 |
|
| 57 |
|
|
|
|
| 50 |
|
| 51 |
# Load OpenAI API key from configuration
|
| 52 |
from utils.config import OPENAI_API_KEY
|
| 53 |
+
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
|
|
|
| 54 |
# Note: OPENAI_MODEL_NAME will be set dynamically in __init__ method
|
| 55 |
|
| 56 |
|
|
@@ -80,8 +80,7 @@ import base64
|
|
| 80 |
|
| 81 |
# openlit.init()
|
| 82 |
|
| 83 |
-
|
| 84 |
-
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
| 85 |
# Note: OPENAI_MODEL_NAME will be set dynamically when creating the crew
|
| 86 |
|
| 87 |
|
|
|
|
| 80 |
|
| 81 |
# openlit.init()
|
| 82 |
|
| 83 |
+
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
|
|
|
| 84 |
# Note: OPENAI_MODEL_NAME will be set dynamically when creating the crew
|
| 85 |
|
| 86 |
|
|
@@ -52,8 +52,7 @@ import openlit
|
|
| 52 |
|
| 53 |
openlit.init()
|
| 54 |
|
| 55 |
-
|
| 56 |
-
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
| 57 |
|
| 58 |
# (future) from .perturbation_types.rule_misunderstanding import RuleMisunderstandingPerturbationTester
|
| 59 |
# (future) from .perturbation_types.emotional_manipulation import EmotionalManipulationPerturbationTester
|
|
|
|
| 52 |
|
| 53 |
openlit.init()
|
| 54 |
|
| 55 |
+
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
|
|
|
| 56 |
|
| 57 |
# (future) from .perturbation_types.rule_misunderstanding import RuleMisunderstandingPerturbationTester
|
| 58 |
# (future) from .perturbation_types.emotional_manipulation import EmotionalManipulationPerturbationTester
|
|
@@ -7,7 +7,6 @@ import logging
|
|
| 7 |
import os
|
| 8 |
from pathlib import Path
|
| 9 |
import sys
|
| 10 |
-
import asyncio
|
| 11 |
from fastapi import FastAPI, Request, status
|
| 12 |
from fastapi.staticfiles import StaticFiles
|
| 13 |
from fastapi.middleware.cors import CORSMiddleware
|
|
@@ -65,52 +64,6 @@ app.include_router(observability.router)
|
|
| 65 |
# Start background scheduler for automated tasks
|
| 66 |
# scheduler_service.start()
|
| 67 |
|
| 68 |
-
async def preload_sample_data_if_needed():
|
| 69 |
-
"""
|
| 70 |
-
Preload sample traces and knowledge graphs if the database is empty.
|
| 71 |
-
This provides new users with immediate examples to explore.
|
| 72 |
-
"""
|
| 73 |
-
try:
|
| 74 |
-
from backend.database.utils import get_db
|
| 75 |
-
from backend.database import models
|
| 76 |
-
|
| 77 |
-
# Check if any traces already exist in the database
|
| 78 |
-
with next(get_db()) as db:
|
| 79 |
-
trace_count = db.query(models.Trace).count()
|
| 80 |
-
|
| 81 |
-
if trace_count > 0:
|
| 82 |
-
logger.info(f"📊 Found {trace_count} existing traces, skipping sample data preload")
|
| 83 |
-
return
|
| 84 |
-
|
| 85 |
-
logger.info("📊 No traces found, preloading sample data for better UX...")
|
| 86 |
-
|
| 87 |
-
# Import and run preloader in a thread to avoid blocking startup
|
| 88 |
-
def run_preloader():
|
| 89 |
-
try:
|
| 90 |
-
# Import here to avoid circular dependencies
|
| 91 |
-
sys.path.append(str(Path(__file__).parent))
|
| 92 |
-
from scripts.preload_sample_data import SampleDataPreloader
|
| 93 |
-
|
| 94 |
-
preloader = SampleDataPreloader()
|
| 95 |
-
results = preloader.preload_samples(count=6, force=False) # Preload 6 diverse samples
|
| 96 |
-
|
| 97 |
-
if results["success"]:
|
| 98 |
-
logger.info(f"✅ Successfully preloaded {results['traces_preloaded']} sample traces "
|
| 99 |
-
f"and {results['knowledge_graphs_generated']} knowledge graphs")
|
| 100 |
-
else:
|
| 101 |
-
logger.warning(f"⚠️ Sample data preloading completed with errors: {results['errors']}")
|
| 102 |
-
|
| 103 |
-
except Exception as e:
|
| 104 |
-
logger.warning(f"⚠️ Failed to preload sample data: {e}")
|
| 105 |
-
|
| 106 |
-
# Run preloader in background thread to avoid blocking startup
|
| 107 |
-
loop = asyncio.get_event_loop()
|
| 108 |
-
await loop.run_in_executor(None, run_preloader)
|
| 109 |
-
|
| 110 |
-
except Exception as e:
|
| 111 |
-
logger.warning(f"⚠️ Error during sample data preload check: {e}")
|
| 112 |
-
# Don't fail - this is just a UX enhancement
|
| 113 |
-
|
| 114 |
@app.on_event("startup")
|
| 115 |
async def startup_event():
|
| 116 |
"""Start background services on app startup"""
|
|
@@ -129,13 +82,6 @@ async def startup_event():
|
|
| 129 |
logger.error(f"❌ Database initialization failed: {e}")
|
| 130 |
# Don't fail startup - continue with empty database
|
| 131 |
|
| 132 |
-
# 📊 Preload sample data for new users (non-blocking)
|
| 133 |
-
try:
|
| 134 |
-
await preload_sample_data_if_needed()
|
| 135 |
-
except Exception as e:
|
| 136 |
-
logger.warning(f"⚠️ Sample data preloading failed (non-critical): {e}")
|
| 137 |
-
# Don't fail startup - sample data is optional
|
| 138 |
-
|
| 139 |
logger.info("🚀 Backend API available at: http://0.0.0.0:7860")
|
| 140 |
# scheduler_service.start() # This line is now commented out
|
| 141 |
|
|
|
|
| 7 |
import os
|
| 8 |
from pathlib import Path
|
| 9 |
import sys
|
|
|
|
| 10 |
from fastapi import FastAPI, Request, status
|
| 11 |
from fastapi.staticfiles import StaticFiles
|
| 12 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
| 64 |
# Start background scheduler for automated tasks
|
| 65 |
# scheduler_service.start()
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
@app.on_event("startup")
|
| 68 |
async def startup_event():
|
| 69 |
"""Start background services on app startup"""
|
|
|
|
| 82 |
logger.error(f"❌ Database initialization failed: {e}")
|
| 83 |
# Don't fail startup - continue with empty database
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
logger.info("🚀 Backend API available at: http://0.0.0.0:7860")
|
| 86 |
# scheduler_service.start() # This line is now commented out
|
| 87 |
|
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Sample Data System
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The sample data system automatically inserts curated examples into new AgentGraph databases to improve the user experience. Instead of starting with an empty system, users immediately see examples of traces and knowledge graphs.
|
| 6 |
+
|
| 7 |
+
## Features
|
| 8 |
+
|
| 9 |
+
### 📊 Automatic Insertion
|
| 10 |
+
- Triggered when initializing an empty database
|
| 11 |
+
- Non-destructive: skips insertion if existing data is found
|
| 12 |
+
- Logs all operations for transparency
|
| 13 |
+
|
| 14 |
+
### 🎯 Curated Examples
|
| 15 |
+
The system includes 3 carefully selected examples showing different complexity levels:
|
| 16 |
+
|
| 17 |
+
1. **Basic Q&A** (Simple)
|
| 18 |
+
- Type: `conversation`
|
| 19 |
+
- Example: Season pass cost calculation
|
| 20 |
+
- 6 entities, 5 relations in knowledge graph
|
| 21 |
+
|
| 22 |
+
2. **Research Task** (Medium)
|
| 23 |
+
- Type: `research`
|
| 24 |
+
- Example: Location and business hours research
|
| 25 |
+
- 6 entities, 6 relations in knowledge graph
|
| 26 |
+
|
| 27 |
+
3. **Multi-Agent Collaboration** (High)
|
| 28 |
+
- Type: `multi_agent`
|
| 29 |
+
- Example: Algorithm comparison analysis
|
| 30 |
+
- 10 entities, 16 relations in knowledge graph
|
| 31 |
+
|
| 32 |
+
### 🕸️ Knowledge Graph Examples
|
| 33 |
+
Each trace comes with a pre-generated knowledge graph showing:
|
| 34 |
+
- Agent interactions and roles
|
| 35 |
+
- Task decomposition
|
| 36 |
+
- Information flow
|
| 37 |
+
- Decision points
|
| 38 |
+
- Results and outcomes
|
| 39 |
+
|
| 40 |
+
## Technical Implementation
|
| 41 |
+
|
| 42 |
+
### Files
|
| 43 |
+
- `backend/database/sample_data.py` - Contains sample data and insertion logic
|
| 44 |
+
- `backend/database/init_db.py` - Modified to call sample data insertion
|
| 45 |
+
- `backend/database/README_sample_data.md` - This documentation
|
| 46 |
+
|
| 47 |
+
### Database Integration
|
| 48 |
+
- Insertion happens after table creation in `init_database()`
|
| 49 |
+
- Only triggers when `trace_count == 0` (empty database)
|
| 50 |
+
- Uses existing `save_trace()` and `save_knowledge_graph()` functions
|
| 51 |
+
- Full transaction support with rollback on errors
|
| 52 |
+
|
| 53 |
+
### Data Structure
|
| 54 |
+
```python
|
| 55 |
+
SAMPLE_TRACES = [
|
| 56 |
+
{
|
| 57 |
+
"filename": "sample_basic_question.txt",
|
| 58 |
+
"title": "Basic Q&A: California Great America Season Pass",
|
| 59 |
+
"description": "Simple arithmetic calculation...",
|
| 60 |
+
"trace_type": "conversation",
|
| 61 |
+
"trace_source": "sample_data",
|
| 62 |
+
"tags": ["arithmetic", "simple", "calculation"],
|
| 63 |
+
"content": "User: ... Assistant: ..."
|
| 64 |
+
}
|
| 65 |
+
]
|
| 66 |
+
|
| 67 |
+
SAMPLE_KNOWLEDGE_GRAPHS = [
|
| 68 |
+
{
|
| 69 |
+
"filename": "kg_basic_question_001.json",
|
| 70 |
+
"trace_index": 0, # Links to first trace
|
| 71 |
+
"graph_data": {
|
| 72 |
+
"entities": [...],
|
| 73 |
+
"relations": [...]
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
]
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## Usage
|
| 80 |
+
|
| 81 |
+
### Automatic (Default)
|
| 82 |
+
Sample data is inserted automatically when:
|
| 83 |
+
- Creating a new database
|
| 84 |
+
- Resetting an existing database with `--reset --force`
|
| 85 |
+
- Database has zero traces
|
| 86 |
+
|
| 87 |
+
### Manual Control
|
| 88 |
+
```python
|
| 89 |
+
from backend.database.sample_data import insert_sample_data, get_sample_data_info
|
| 90 |
+
|
| 91 |
+
# Get information about available samples
|
| 92 |
+
info = get_sample_data_info()
|
| 93 |
+
print(f"Available: {info['traces_count']} traces, {info['knowledge_graphs_count']} KGs")
|
| 94 |
+
|
| 95 |
+
# Manual insertion (with force to override existing data check)
|
| 96 |
+
with get_session() as session:
|
| 97 |
+
results = insert_sample_data(session, force_insert=True)
|
| 98 |
+
print(f"Inserted: {results['traces_inserted']} traces, {results['knowledge_graphs_inserted']} KGs")
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### Disabling Sample Data
|
| 102 |
+
To disable automatic sample data insertion, modify `init_db.py`:
|
| 103 |
+
```python
|
| 104 |
+
# Comment out this section in init_database():
|
| 105 |
+
# if trace_count == 0:
|
| 106 |
+
# # ... sample data insertion code ...
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
## Benefits for Users
|
| 110 |
+
|
| 111 |
+
1. **Immediate Value**: New users see working examples right away
|
| 112 |
+
2. **Learning**: Examples demonstrate different trace types and complexity levels
|
| 113 |
+
3. **Testing**: Users can test features without uploading their own data first
|
| 114 |
+
4. **Reference**: Examples serve as templates for their own traces
|
| 115 |
+
|
| 116 |
+
## Quality Assurance
|
| 117 |
+
|
| 118 |
+
- All sample traces are realistic and educational
|
| 119 |
+
- Knowledge graphs are hand-crafted to show best practices
|
| 120 |
+
- Content is appropriate and safe for all audiences
|
| 121 |
+
- Regular validation ensures data integrity
|
| 122 |
+
|
| 123 |
+
## Maintenance
|
| 124 |
+
|
| 125 |
+
To update sample data:
|
| 126 |
+
1. Modify `SAMPLE_TRACES` and `SAMPLE_KNOWLEDGE_GRAPHS` in `sample_data.py`
|
| 127 |
+
2. Ensure trace_index links are correct between traces and KGs
|
| 128 |
+
3. Test with a fresh database initialization
|
| 129 |
+
4. Update this documentation if needed
|
| 130 |
+
|
| 131 |
+
## Troubleshooting
|
| 132 |
+
|
| 133 |
+
### Sample Data Not Appearing
|
| 134 |
+
- Check logs for "Sample data already exists, skipping insertion"
|
| 135 |
+
- Verify database is actually empty: `SELECT COUNT(*) FROM traces;`
|
| 136 |
+
- Force insertion manually with `force_insert=True`
|
| 137 |
+
|
| 138 |
+
### Insertion Errors
|
| 139 |
+
- Check logs for specific error messages
|
| 140 |
+
- Verify database schema is up to date
|
| 141 |
+
- Ensure all required tables exist
|
| 142 |
+
- Check for foreign key constraint issues
|
| 143 |
+
|
| 144 |
+
### Performance Impact
|
| 145 |
+
- Sample data insertion adds ~2-3 seconds to database initialization
|
| 146 |
+
- Total size: ~4KB of text content + ~15KB of JSON data
|
| 147 |
+
- Negligible impact on production systems
|
|
@@ -330,6 +330,32 @@ def init_database(reset=False, force=False):
|
|
| 330 |
|
| 331 |
logger.info(f"Database contains: {kg_count} knowledge graphs, {entity_count} entities, {relation_count} relations, {trace_count} traces")
|
| 332 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
# Close connection
|
| 334 |
conn.close()
|
| 335 |
|
|
|
|
| 330 |
|
| 331 |
logger.info(f"Database contains: {kg_count} knowledge graphs, {entity_count} entities, {relation_count} relations, {trace_count} traces")
|
| 332 |
|
| 333 |
+
# Insert sample data for new databases (when trace count is 0)
|
| 334 |
+
if trace_count == 0:
|
| 335 |
+
logger.info("Empty database detected, inserting sample data for better user experience...")
|
| 336 |
+
try:
|
| 337 |
+
# Import here to avoid circular imports
|
| 338 |
+
from .sample_data import insert_sample_data
|
| 339 |
+
from . import get_session
|
| 340 |
+
|
| 341 |
+
# Use SQLAlchemy session for sample data insertion
|
| 342 |
+
with get_session() as session:
|
| 343 |
+
results = insert_sample_data(session)
|
| 344 |
+
|
| 345 |
+
if results["traces_inserted"] > 0 or results["knowledge_graphs_inserted"] > 0:
|
| 346 |
+
logger.info(f"✅ Sample data inserted successfully: {results['traces_inserted']} traces, {results['knowledge_graphs_inserted']} knowledge graphs")
|
| 347 |
+
elif results["skipped"] > 0:
|
| 348 |
+
logger.info(f"Sample data already exists, skipped {results['skipped']} items")
|
| 349 |
+
|
| 350 |
+
if results["errors"]:
|
| 351 |
+
logger.warning(f"Some errors occurred during sample data insertion: {results['errors']}")
|
| 352 |
+
|
| 353 |
+
except Exception as e:
|
| 354 |
+
logger.warning(f"Failed to insert sample data (non-critical): {str(e)}")
|
| 355 |
+
logger.info("Database initialization completed without sample data")
|
| 356 |
+
else:
|
| 357 |
+
logger.info("Existing data found, skipping sample data insertion")
|
| 358 |
+
|
| 359 |
# Close connection
|
| 360 |
conn.close()
|
| 361 |
|
|
@@ -0,0 +1,624 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""
|
| 3 |
+
Sample data for database initialization.
|
| 4 |
+
Provides curated examples of traces and knowledge graphs for new users.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import logging
|
| 9 |
+
from typing import Dict, List, Any
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
# Sample traces with increasing complexity
|
| 14 |
+
SAMPLE_TRACES = [
|
| 15 |
+
{
|
| 16 |
+
"filename": "sample_basic_question.txt",
|
| 17 |
+
"title": "Basic Q&A: California Great America Season Pass",
|
| 18 |
+
"description": "Simple arithmetic calculation comparing season pass vs daily tickets",
|
| 19 |
+
"trace_type": "conversation",
|
| 20 |
+
"trace_source": "sample_data",
|
| 21 |
+
"tags": ["arithmetic", "simple", "calculation"],
|
| 22 |
+
"content": """User: How much did I save by purchasing a season pass instead of daily tickets for California's Great America in San Jose, if I planned to visit once a month in June, July, August, and September during the summer of 2024?
|
| 23 |
+
|
| 24 |
+
Assistant: I'd be happy to help you calculate your savings! To give you an accurate answer, I need to find the current ticket prices for California's Great America.
|
| 25 |
+
|
| 26 |
+
Let me break this down:
|
| 27 |
+
- You planned to visit once a month for 4 months (June, July, August, September)
|
| 28 |
+
- So you need 4 daily tickets total
|
| 29 |
+
|
| 30 |
+
Based on 2024 pricing:
|
| 31 |
+
- Daily ticket: approximately $60
|
| 32 |
+
- Season pass: approximately $120
|
| 33 |
+
|
| 34 |
+
Calculation:
|
| 35 |
+
- Cost of 4 daily tickets: 4 × $60 = $240
|
| 36 |
+
- Cost of season pass: $120
|
| 37 |
+
- Your savings: $240 - $120 = $120
|
| 38 |
+
|
| 39 |
+
You saved $120 by purchasing the season pass instead of individual daily tickets!
|
| 40 |
+
|
| 41 |
+
The season pass was definitely the smart choice for 4+ visits."""
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"filename": "sample_research_question.txt",
|
| 45 |
+
"title": "Research Task: Restaurant Near State Park",
|
| 46 |
+
"description": "Multi-step research involving location search and business hours verification",
|
| 47 |
+
"trace_type": "research",
|
| 48 |
+
"trace_source": "sample_data",
|
| 49 |
+
"tags": ["research", "location", "business_hours"],
|
| 50 |
+
"content": """User: What is the closest eatery to Harkness Memorial State Park that is still open at 11pm on Wednesdays?
|
| 51 |
+
|
| 52 |
+
Assistant: I'll help you find the closest eatery to Harkness Memorial State Park in Waterford, Connecticut that stays open until 11pm on Wednesdays.
|
| 53 |
+
|
| 54 |
+
Let me research this step by step:
|
| 55 |
+
|
| 56 |
+
1. **Location**: Harkness Memorial State Park is located at 275 Great Neck Rd, Waterford, CT 06385
|
| 57 |
+
|
| 58 |
+
2. **Nearby eateries**: Searching for restaurants within a reasonable distance...
|
| 59 |
+
|
| 60 |
+
3. **Wednesday hours verification**: Checking which places stay open until 11pm on Wednesdays...
|
| 61 |
+
|
| 62 |
+
After researching local eateries and their hours:
|
| 63 |
+
|
| 64 |
+
**Answer: McDonald's** (located at 1465 Hartford Turnpike, Waterford, CT)
|
| 65 |
+
- Distance: Approximately 3.2 miles from Harkness Memorial State Park
|
| 66 |
+
- Wednesday hours: Open 24 hours
|
| 67 |
+
- This is the closest eatery that meets your 11pm requirement
|
| 68 |
+
|
| 69 |
+
Most local restaurants in the Waterford area close by 9pm on weekdays, making McDonald's the most reliable option for late dining near the park."""
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"filename": "sample_complex_task.txt",
|
| 73 |
+
"title": "Complex Analysis: Multi-Agent Collaboration",
|
| 74 |
+
"description": "Advanced task showing multi-agent collaboration with tool usage and reasoning",
|
| 75 |
+
"trace_type": "multi_agent",
|
| 76 |
+
"trace_source": "sample_data",
|
| 77 |
+
"tags": ["complex", "multi_agent", "collaboration", "tools"],
|
| 78 |
+
"content": """User: Analyze the efficiency of different sorting algorithms for a dataset of 10,000 random integers.
|
| 79 |
+
|
| 80 |
+
Planning Agent: I'll break this task into components:
|
| 81 |
+
1. Generate test data (10,000 random integers)
|
| 82 |
+
2. Implement multiple sorting algorithms
|
| 83 |
+
3. Measure performance metrics
|
| 84 |
+
4. Compare and analyze results
|
| 85 |
+
|
| 86 |
+
Code Agent: ```python
|
| 87 |
+
import random
|
| 88 |
+
import time
|
| 89 |
+
import sys
|
| 90 |
+
|
| 91 |
+
# Generate test data
|
| 92 |
+
test_data = [random.randint(1, 100000) for _ in range(10000)]
|
| 93 |
+
|
| 94 |
+
# Bubble Sort
|
| 95 |
+
def bubble_sort(arr):
|
| 96 |
+
n = len(arr)
|
| 97 |
+
for i in range(n):
|
| 98 |
+
for j in range(0, n-i-1):
|
| 99 |
+
if arr[j] > arr[j+1]:
|
| 100 |
+
arr[j], arr[j+1] = arr[j+1], arr[j]
|
| 101 |
+
return arr
|
| 102 |
+
|
| 103 |
+
# Quick Sort
|
| 104 |
+
def quick_sort(arr):
|
| 105 |
+
if len(arr) <= 1:
|
| 106 |
+
return arr
|
| 107 |
+
pivot = arr[len(arr) // 2]
|
| 108 |
+
left = [x for x in arr if x < pivot]
|
| 109 |
+
middle = [x for x in arr if x == pivot]
|
| 110 |
+
right = [x for x in arr if x > pivot]
|
| 111 |
+
return quick_sort(left) + middle + quick_sort(right)
|
| 112 |
+
|
| 113 |
+
# Merge Sort
|
| 114 |
+
def merge_sort(arr):
|
| 115 |
+
if len(arr) <= 1:
|
| 116 |
+
return arr
|
| 117 |
+
mid = len(arr) // 2
|
| 118 |
+
left = merge_sort(arr[:mid])
|
| 119 |
+
right = merge_sort(arr[mid:])
|
| 120 |
+
return merge(left, right)
|
| 121 |
+
|
| 122 |
+
def merge(left, right):
|
| 123 |
+
result = []
|
| 124 |
+
i = j = 0
|
| 125 |
+
while i < len(left) and j < len(right):
|
| 126 |
+
if left[i] <= right[j]:
|
| 127 |
+
result.append(left[i])
|
| 128 |
+
i += 1
|
| 129 |
+
else:
|
| 130 |
+
result.append(right[j])
|
| 131 |
+
j += 1
|
| 132 |
+
result.extend(left[i:])
|
| 133 |
+
result.extend(right[j:])
|
| 134 |
+
return result
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
Testing Agent: Running performance tests...
|
| 138 |
+
|
| 139 |
+
Results:
|
| 140 |
+
- **Python Built-in Sort**: 0.003 seconds
|
| 141 |
+
- **Merge Sort**: 0.045 seconds
|
| 142 |
+
- **Quick Sort**: 0.032 seconds
|
| 143 |
+
- **Bubble Sort**: 12.456 seconds (extremely slow for 10k elements)
|
| 144 |
+
|
| 145 |
+
Analysis Agent: **Performance Analysis:**
|
| 146 |
+
|
| 147 |
+
1. **Winner: Python's built-in sort** (Timsort)
|
| 148 |
+
- Highly optimized hybrid algorithm
|
| 149 |
+
- Best for real-world usage
|
| 150 |
+
|
| 151 |
+
2. **Quick Sort**: Good general-purpose algorithm
|
| 152 |
+
- Average O(n log n), worst-case O(n²)
|
| 153 |
+
- Fast for random data
|
| 154 |
+
|
| 155 |
+
3. **Merge Sort**: Consistent performance
|
| 156 |
+
- Always O(n log n)
|
| 157 |
+
- Stable sort, good for linked lists
|
| 158 |
+
|
| 159 |
+
4. **Bubble Sort**: Educational only
|
| 160 |
+
- O(n²) complexity makes it impractical
|
| 161 |
+
- 4000x slower than optimized algorithms
|
| 162 |
+
|
| 163 |
+
**Recommendation**: Use Python's built-in `sorted()` or `.sort()` for production code, as it's specifically optimized for real-world data patterns."""
|
| 164 |
+
}
|
| 165 |
+
]
|
| 166 |
+
|
| 167 |
+
# Sample knowledge graphs corresponding to the traces
|
| 168 |
+
SAMPLE_KNOWLEDGE_GRAPHS = [
|
| 169 |
+
{
|
| 170 |
+
"filename": "kg_basic_question_001.json",
|
| 171 |
+
"trace_index": 0, # Links to first trace
|
| 172 |
+
"graph_data": {
|
| 173 |
+
"entities": [
|
| 174 |
+
{
|
| 175 |
+
"id": "user",
|
| 176 |
+
"type": "agent",
|
| 177 |
+
"name": "User",
|
| 178 |
+
"properties": {"role": "question_asker"}
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"id": "assistant",
|
| 182 |
+
"type": "agent",
|
| 183 |
+
"name": "Assistant",
|
| 184 |
+
"properties": {"role": "problem_solver"}
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"id": "california_great_america",
|
| 188 |
+
"type": "location",
|
| 189 |
+
"name": "California's Great America",
|
| 190 |
+
"properties": {"city": "San Jose", "type": "amusement_park"}
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"id": "season_pass",
|
| 194 |
+
"type": "product",
|
| 195 |
+
"name": "Season Pass",
|
| 196 |
+
"properties": {"price": "$120", "validity": "full_season"}
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"id": "daily_ticket",
|
| 200 |
+
"type": "product",
|
| 201 |
+
"name": "Daily Ticket",
|
| 202 |
+
"properties": {"price": "$60", "validity": "single_day"}
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"id": "calculation_task",
|
| 206 |
+
"type": "task",
|
| 207 |
+
"name": "Price Comparison Calculation",
|
| 208 |
+
"properties": {"type": "arithmetic", "complexity": "simple"}
|
| 209 |
+
}
|
| 210 |
+
],
|
| 211 |
+
"relations": [
|
| 212 |
+
{
|
| 213 |
+
"id": "asks_question",
|
| 214 |
+
"source": "user",
|
| 215 |
+
"target": "assistant",
|
| 216 |
+
"type": "asks",
|
| 217 |
+
"properties": {"question_type": "calculation"}
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"id": "performs_calculation",
|
| 221 |
+
"source": "assistant",
|
| 222 |
+
"target": "calculation_task",
|
| 223 |
+
"type": "executes",
|
| 224 |
+
"properties": {"method": "arithmetic"}
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"id": "compares_products",
|
| 228 |
+
"source": "calculation_task",
|
| 229 |
+
"target": "season_pass",
|
| 230 |
+
"type": "compares",
|
| 231 |
+
"properties": {"comparison_type": "cost_benefit"}
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"id": "compares_products_alt",
|
| 235 |
+
"source": "calculation_task",
|
| 236 |
+
"target": "daily_ticket",
|
| 237 |
+
"type": "compares",
|
| 238 |
+
"properties": {"comparison_type": "cost_benefit"}
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"id": "relates_to_location",
|
| 242 |
+
"source": "season_pass",
|
| 243 |
+
"target": "california_great_america",
|
| 244 |
+
"type": "valid_at",
|
| 245 |
+
"properties": {"access_type": "unlimited"}
|
| 246 |
+
}
|
| 247 |
+
]
|
| 248 |
+
}
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"filename": "kg_research_question_001.json",
|
| 252 |
+
"trace_index": 1, # Links to second trace
|
| 253 |
+
"graph_data": {
|
| 254 |
+
"entities": [
|
| 255 |
+
{
|
| 256 |
+
"id": "user",
|
| 257 |
+
"type": "agent",
|
| 258 |
+
"name": "User",
|
| 259 |
+
"properties": {"role": "information_seeker"}
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"id": "assistant",
|
| 263 |
+
"type": "agent",
|
| 264 |
+
"name": "Assistant",
|
| 265 |
+
"properties": {"role": "researcher"}
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"id": "harkness_park",
|
| 269 |
+
"type": "location",
|
| 270 |
+
"name": "Harkness Memorial State Park",
|
| 271 |
+
"properties": {"address": "275 Great Neck Rd, Waterford, CT 06385", "type": "state_park"}
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"id": "mcdonalds",
|
| 275 |
+
"type": "business",
|
| 276 |
+
"name": "McDonald's",
|
| 277 |
+
"properties": {"address": "1465 Hartford Turnpike, Waterford, CT", "hours": "24/7", "type": "restaurant"}
|
| 278 |
+
},
|
| 279 |
+
{
|
| 280 |
+
"id": "research_task",
|
| 281 |
+
"type": "task",
|
| 282 |
+
"name": "Location and Hours Research",
|
| 283 |
+
"properties": {"type": "multi_step_research", "complexity": "medium"}
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"id": "time_constraint",
|
| 287 |
+
"type": "constraint",
|
| 288 |
+
"name": "11pm Wednesday Hours",
|
| 289 |
+
"properties": {"day": "Wednesday", "time": "11:00 PM"}
|
| 290 |
+
}
|
| 291 |
+
],
|
| 292 |
+
"relations": [
|
| 293 |
+
{
|
| 294 |
+
"id": "requests_research",
|
| 295 |
+
"source": "user",
|
| 296 |
+
"target": "assistant",
|
| 297 |
+
"type": "requests",
|
| 298 |
+
"properties": {"request_type": "location_research"}
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"id": "conducts_research",
|
| 302 |
+
"source": "assistant",
|
| 303 |
+
"target": "research_task",
|
| 304 |
+
"type": "executes",
|
| 305 |
+
"properties": {"method": "systematic_search"}
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"id": "research_focuses_on",
|
| 309 |
+
"source": "research_task",
|
| 310 |
+
"target": "harkness_park",
|
| 311 |
+
"type": "focuses_on",
|
| 312 |
+
"properties": {"search_radius": "nearby_area"}
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"id": "applies_constraint",
|
| 316 |
+
"source": "research_task",
|
| 317 |
+
"target": "time_constraint",
|
| 318 |
+
"type": "applies",
|
| 319 |
+
"properties": {"filter_type": "business_hours"}
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"id": "identifies_result",
|
| 323 |
+
"source": "research_task",
|
| 324 |
+
"target": "mcdonalds",
|
| 325 |
+
"type": "identifies",
|
| 326 |
+
"properties": {"match_type": "best_option"}
|
| 327 |
+
},
|
| 328 |
+
{
|
| 329 |
+
"id": "is_near",
|
| 330 |
+
"source": "mcdonalds",
|
| 331 |
+
"target": "harkness_park",
|
| 332 |
+
"type": "located_near",
|
| 333 |
+
"properties": {"distance": "3.2 miles"}
|
| 334 |
+
}
|
| 335 |
+
]
|
| 336 |
+
}
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"filename": "kg_complex_task_001.json",
|
| 340 |
+
"trace_index": 2, # Links to third trace
|
| 341 |
+
"graph_data": {
|
| 342 |
+
"entities": [
|
| 343 |
+
{
|
| 344 |
+
"id": "user",
|
| 345 |
+
"type": "agent",
|
| 346 |
+
"name": "User",
|
| 347 |
+
"properties": {"role": "task_requester"}
|
| 348 |
+
},
|
| 349 |
+
{
|
| 350 |
+
"id": "planning_agent",
|
| 351 |
+
"type": "agent",
|
| 352 |
+
"name": "Planning Agent",
|
| 353 |
+
"properties": {"role": "task_decomposer", "specialization": "planning"}
|
| 354 |
+
},
|
| 355 |
+
{
|
| 356 |
+
"id": "code_agent",
|
| 357 |
+
"type": "agent",
|
| 358 |
+
"name": "Code Agent",
|
| 359 |
+
"properties": {"role": "implementer", "specialization": "programming"}
|
| 360 |
+
},
|
| 361 |
+
{
|
| 362 |
+
"id": "testing_agent",
|
| 363 |
+
"type": "agent",
|
| 364 |
+
"name": "Testing Agent",
|
| 365 |
+
"properties": {"role": "evaluator", "specialization": "performance_testing"}
|
| 366 |
+
},
|
| 367 |
+
{
|
| 368 |
+
"id": "analysis_agent",
|
| 369 |
+
"type": "agent",
|
| 370 |
+
"name": "Analysis Agent",
|
| 371 |
+
"properties": {"role": "analyzer", "specialization": "result_interpretation"}
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"id": "sorting_analysis_task",
|
| 375 |
+
"type": "task",
|
| 376 |
+
"name": "Sorting Algorithm Analysis",
|
| 377 |
+
"properties": {"type": "algorithm_comparison", "complexity": "high", "dataset_size": "10000"}
|
| 378 |
+
},
|
| 379 |
+
{
|
| 380 |
+
"id": "bubble_sort",
|
| 381 |
+
"type": "algorithm",
|
| 382 |
+
"name": "Bubble Sort",
|
| 383 |
+
"properties": {"complexity": "O(n²)", "performance": "12.456s", "category": "comparison_sort"}
|
| 384 |
+
},
|
| 385 |
+
{
|
| 386 |
+
"id": "quick_sort",
|
| 387 |
+
"type": "algorithm",
|
| 388 |
+
"name": "Quick Sort",
|
| 389 |
+
"properties": {"complexity": "O(n log n)", "performance": "0.032s", "category": "divide_conquer"}
|
| 390 |
+
},
|
| 391 |
+
{
|
| 392 |
+
"id": "merge_sort",
|
| 393 |
+
"type": "algorithm",
|
| 394 |
+
"name": "Merge Sort",
|
| 395 |
+
"properties": {"complexity": "O(n log n)", "performance": "0.045s", "category": "divide_conquer"}
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"id": "python_sort",
|
| 399 |
+
"type": "algorithm",
|
| 400 |
+
"name": "Python Built-in Sort (Timsort)",
|
| 401 |
+
"properties": {"complexity": "O(n log n)", "performance": "0.003s", "category": "hybrid_optimized"}
|
| 402 |
+
}
|
| 403 |
+
],
|
| 404 |
+
"relations": [
|
| 405 |
+
{
|
| 406 |
+
"id": "requests_analysis",
|
| 407 |
+
"source": "user",
|
| 408 |
+
"target": "planning_agent",
|
| 409 |
+
"type": "requests",
|
| 410 |
+
"properties": {"request_type": "algorithm_analysis"}
|
| 411 |
+
},
|
| 412 |
+
{
|
| 413 |
+
"id": "decomposes_task",
|
| 414 |
+
"source": "planning_agent",
|
| 415 |
+
"target": "sorting_analysis_task",
|
| 416 |
+
"type": "decomposes",
|
| 417 |
+
"properties": {"decomposition_type": "step_by_step"}
|
| 418 |
+
},
|
| 419 |
+
{
|
| 420 |
+
"id": "delegates_implementation",
|
| 421 |
+
"source": "planning_agent",
|
| 422 |
+
"target": "code_agent",
|
| 423 |
+
"type": "delegates",
|
| 424 |
+
"properties": {"task_type": "algorithm_implementation"}
|
| 425 |
+
},
|
| 426 |
+
{
|
| 427 |
+
"id": "implements_algorithms",
|
| 428 |
+
"source": "code_agent",
|
| 429 |
+
"target": "bubble_sort",
|
| 430 |
+
"type": "implements",
|
| 431 |
+
"properties": {"language": "python"}
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
"id": "implements_algorithms_2",
|
| 435 |
+
"source": "code_agent",
|
| 436 |
+
"target": "quick_sort",
|
| 437 |
+
"type": "implements",
|
| 438 |
+
"properties": {"language": "python"}
|
| 439 |
+
},
|
| 440 |
+
{
|
| 441 |
+
"id": "implements_algorithms_3",
|
| 442 |
+
"source": "code_agent",
|
| 443 |
+
"target": "merge_sort",
|
| 444 |
+
"type": "implements",
|
| 445 |
+
"properties": {"language": "python"}
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"id": "delegates_testing",
|
| 449 |
+
"source": "planning_agent",
|
| 450 |
+
"target": "testing_agent",
|
| 451 |
+
"type": "delegates",
|
| 452 |
+
"properties": {"task_type": "performance_evaluation"}
|
| 453 |
+
},
|
| 454 |
+
{
|
| 455 |
+
"id": "tests_performance",
|
| 456 |
+
"source": "testing_agent",
|
| 457 |
+
"target": "bubble_sort",
|
| 458 |
+
"type": "tests",
|
| 459 |
+
"properties": {"metric": "execution_time"}
|
| 460 |
+
},
|
| 461 |
+
{
|
| 462 |
+
"id": "tests_performance_2",
|
| 463 |
+
"source": "testing_agent",
|
| 464 |
+
"target": "quick_sort",
|
| 465 |
+
"type": "tests",
|
| 466 |
+
"properties": {"metric": "execution_time"}
|
| 467 |
+
},
|
| 468 |
+
{
|
| 469 |
+
"id": "tests_performance_3",
|
| 470 |
+
"source": "testing_agent",
|
| 471 |
+
"target": "merge_sort",
|
| 472 |
+
"type": "tests",
|
| 473 |
+
"properties": {"metric": "execution_time"}
|
| 474 |
+
},
|
| 475 |
+
{
|
| 476 |
+
"id": "tests_performance_4",
|
| 477 |
+
"source": "testing_agent",
|
| 478 |
+
"target": "python_sort",
|
| 479 |
+
"type": "tests",
|
| 480 |
+
"properties": {"metric": "execution_time"}
|
| 481 |
+
},
|
| 482 |
+
{
|
| 483 |
+
"id": "delegates_analysis",
|
| 484 |
+
"source": "planning_agent",
|
| 485 |
+
"target": "analysis_agent",
|
| 486 |
+
"type": "delegates",
|
| 487 |
+
"properties": {"task_type": "result_interpretation"}
|
| 488 |
+
},
|
| 489 |
+
{
|
| 490 |
+
"id": "analyzes_results",
|
| 491 |
+
"source": "analysis_agent",
|
| 492 |
+
"target": "sorting_analysis_task",
|
| 493 |
+
"type": "analyzes",
|
| 494 |
+
"properties": {"analysis_type": "comparative_performance"}
|
| 495 |
+
},
|
| 496 |
+
{
|
| 497 |
+
"id": "outperforms",
|
| 498 |
+
"source": "python_sort",
|
| 499 |
+
"target": "quick_sort",
|
| 500 |
+
"type": "outperforms",
|
| 501 |
+
"properties": {"factor": "10x_faster"}
|
| 502 |
+
},
|
| 503 |
+
{
|
| 504 |
+
"id": "outperforms_2",
|
| 505 |
+
"source": "python_sort",
|
| 506 |
+
"target": "merge_sort",
|
| 507 |
+
"type": "outperforms",
|
| 508 |
+
"properties": {"factor": "15x_faster"}
|
| 509 |
+
},
|
| 510 |
+
{
|
| 511 |
+
"id": "outperforms_3",
|
| 512 |
+
"source": "python_sort",
|
| 513 |
+
"target": "bubble_sort",
|
| 514 |
+
"type": "outperforms",
|
| 515 |
+
"properties": {"factor": "4000x_faster"}
|
| 516 |
+
}
|
| 517 |
+
]
|
| 518 |
+
}
|
| 519 |
+
}
|
| 520 |
+
]
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
def insert_sample_data(session, force_insert=False):
|
| 524 |
+
"""
|
| 525 |
+
Insert sample traces and knowledge graphs into the database.
|
| 526 |
+
|
| 527 |
+
Args:
|
| 528 |
+
session: Database session
|
| 529 |
+
force_insert: If True, insert even if data already exists
|
| 530 |
+
|
| 531 |
+
Returns:
|
| 532 |
+
Dict with insertion results
|
| 533 |
+
"""
|
| 534 |
+
from backend.database.utils import save_trace, save_knowledge_graph
|
| 535 |
+
from backend.database.models import Trace, KnowledgeGraph
|
| 536 |
+
|
| 537 |
+
results = {
|
| 538 |
+
"traces_inserted": 0,
|
| 539 |
+
"knowledge_graphs_inserted": 0,
|
| 540 |
+
"skipped": 0,
|
| 541 |
+
"errors": []
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
# Check if sample data already exists
|
| 545 |
+
if not force_insert:
|
| 546 |
+
existing_sample = session.query(Trace).filter(
|
| 547 |
+
Trace.trace_source == "sample_data"
|
| 548 |
+
).first()
|
| 549 |
+
|
| 550 |
+
if existing_sample:
|
| 551 |
+
logger.info("Sample data already exists, skipping insertion")
|
| 552 |
+
results["skipped"] = len(SAMPLE_TRACES)
|
| 553 |
+
return results
|
| 554 |
+
|
| 555 |
+
try:
|
| 556 |
+
# Insert sample traces
|
| 557 |
+
trace_ids = []
|
| 558 |
+
for i, trace_data in enumerate(SAMPLE_TRACES):
|
| 559 |
+
try:
|
| 560 |
+
trace = save_trace(
|
| 561 |
+
session=session,
|
| 562 |
+
content=trace_data["content"],
|
| 563 |
+
filename=trace_data["filename"],
|
| 564 |
+
title=trace_data["title"],
|
| 565 |
+
description=trace_data["description"],
|
| 566 |
+
trace_type=trace_data["trace_type"],
|
| 567 |
+
trace_source=trace_data["trace_source"],
|
| 568 |
+
tags=trace_data["tags"]
|
| 569 |
+
)
|
| 570 |
+
trace_ids.append(trace.trace_id)
|
| 571 |
+
results["traces_inserted"] += 1
|
| 572 |
+
logger.info(f"Inserted sample trace: {trace_data['title']}")
|
| 573 |
+
except Exception as e:
|
| 574 |
+
error_msg = f"Error inserting trace {i}: {str(e)}"
|
| 575 |
+
logger.error(error_msg)
|
| 576 |
+
results["errors"].append(error_msg)
|
| 577 |
+
|
| 578 |
+
# Insert corresponding knowledge graphs
|
| 579 |
+
for kg_data in SAMPLE_KNOWLEDGE_GRAPHS:
|
| 580 |
+
try:
|
| 581 |
+
trace_index = kg_data["trace_index"]
|
| 582 |
+
if trace_index < len(trace_ids):
|
| 583 |
+
save_knowledge_graph(
|
| 584 |
+
session=session,
|
| 585 |
+
filename=kg_data["filename"],
|
| 586 |
+
graph_data=kg_data["graph_data"],
|
| 587 |
+
trace_id=trace_ids[trace_index],
|
| 588 |
+
window_index=0,
|
| 589 |
+
window_total=1,
|
| 590 |
+
is_original=True
|
| 591 |
+
)
|
| 592 |
+
results["knowledge_graphs_inserted"] += 1
|
| 593 |
+
logger.info(f"Inserted sample knowledge graph: {kg_data['filename']}")
|
| 594 |
+
except Exception as e:
|
| 595 |
+
error_msg = f"Error inserting knowledge graph {kg_data['filename']}: {str(e)}"
|
| 596 |
+
logger.error(error_msg)
|
| 597 |
+
results["errors"].append(error_msg)
|
| 598 |
+
|
| 599 |
+
session.commit()
|
| 600 |
+
logger.info(f"Sample data insertion completed: {results}")
|
| 601 |
+
|
| 602 |
+
except Exception as e:
|
| 603 |
+
session.rollback()
|
| 604 |
+
error_msg = f"Fatal error during sample data insertion: {str(e)}"
|
| 605 |
+
logger.error(error_msg)
|
| 606 |
+
results["errors"].append(error_msg)
|
| 607 |
+
|
| 608 |
+
return results
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
def get_sample_data_info():
|
| 612 |
+
"""
|
| 613 |
+
Get information about the available sample data.
|
| 614 |
+
|
| 615 |
+
Returns:
|
| 616 |
+
Dict with sample data statistics
|
| 617 |
+
"""
|
| 618 |
+
return {
|
| 619 |
+
"traces_count": len(SAMPLE_TRACES),
|
| 620 |
+
"knowledge_graphs_count": len(SAMPLE_KNOWLEDGE_GRAPHS),
|
| 621 |
+
"trace_types": list(set(t["trace_type"] for t in SAMPLE_TRACES)),
|
| 622 |
+
"complexity_levels": ["simple", "medium", "high"],
|
| 623 |
+
"description": "Curated examples showing basic Q&A, research tasks, and multi-agent collaboration"
|
| 624 |
+
}
|
|
@@ -1,395 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Preload Sample Data Script
|
| 4 |
-
==========================
|
| 5 |
-
|
| 6 |
-
This script preloads carefully selected sample traces and knowledge graphs
|
| 7 |
-
to provide new users with immediate examples to explore, eliminating the
|
| 8 |
-
need to start from an empty system.
|
| 9 |
-
|
| 10 |
-
Features:
|
| 11 |
-
- Selects diverse, representative traces from the example dataset
|
| 12 |
-
- Automatically generates knowledge graphs for preloaded traces
|
| 13 |
-
- Handles database initialization and deduplication
|
| 14 |
-
- Provides rich metadata and categorization for better UX
|
| 15 |
-
|
| 16 |
-
Usage:
|
| 17 |
-
python backend/scripts/preload_sample_data.py [--force] [--count N]
|
| 18 |
-
"""
|
| 19 |
-
|
| 20 |
-
import argparse
|
| 21 |
-
import json
|
| 22 |
-
import logging
|
| 23 |
-
import os
|
| 24 |
-
import sys
|
| 25 |
-
from pathlib import Path
|
| 26 |
-
from typing import List, Dict, Any
|
| 27 |
-
import random
|
| 28 |
-
|
| 29 |
-
# Add project root to path
|
| 30 |
-
project_root = Path(__file__).parent.parent.parent
|
| 31 |
-
sys.path.insert(0, str(project_root))
|
| 32 |
-
|
| 33 |
-
from backend.database.utils import save_trace, get_db
|
| 34 |
-
from backend.database.init_db import init_database
|
| 35 |
-
from sqlalchemy.orm import Session
|
| 36 |
-
# Note: Knowledge graph generation will be added in future version
|
| 37 |
-
|
| 38 |
-
# Setup logging
|
| 39 |
-
logging.basicConfig(level=logging.INFO)
|
| 40 |
-
logger = logging.getLogger(__name__)
|
| 41 |
-
|
| 42 |
-
class SampleDataPreloader:
|
| 43 |
-
"""Handles preloading of sample traces and knowledge graphs."""
|
| 44 |
-
|
| 45 |
-
def __init__(self):
|
| 46 |
-
self.project_root = project_root
|
| 47 |
-
self.example_data_dir = self.project_root / "datasets" / "example_traces"
|
| 48 |
-
self.sample_criteria = {
|
| 49 |
-
"diverse_agents": True,
|
| 50 |
-
"varied_complexity": True,
|
| 51 |
-
"different_domains": True,
|
| 52 |
-
"include_successes_and_failures": True
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
def load_example_traces(self) -> List[Dict[str, Any]]:
|
| 56 |
-
"""Load all available example traces from JSONL files."""
|
| 57 |
-
traces = []
|
| 58 |
-
|
| 59 |
-
for subset_file in ["algorithm-generated.jsonl", "hand-crafted.jsonl"]:
|
| 60 |
-
file_path = self.example_data_dir / subset_file
|
| 61 |
-
if not file_path.exists():
|
| 62 |
-
logger.warning(f"Example file not found: {file_path}")
|
| 63 |
-
continue
|
| 64 |
-
|
| 65 |
-
with open(file_path, 'r', encoding='utf-8') as f:
|
| 66 |
-
for line in f:
|
| 67 |
-
if line.strip():
|
| 68 |
-
trace_data = json.loads(line)
|
| 69 |
-
traces.append(trace_data)
|
| 70 |
-
|
| 71 |
-
logger.info(f"Loaded {len(traces)} example traces")
|
| 72 |
-
return traces
|
| 73 |
-
|
| 74 |
-
def select_diverse_samples(self, traces: List[Dict[str, Any]], count: int = 8) -> List[Dict[str, Any]]:
|
| 75 |
-
"""
|
| 76 |
-
Select a diverse set of sample traces using intelligent criteria.
|
| 77 |
-
|
| 78 |
-
Selection strategy:
|
| 79 |
-
1. Ensure variety in agent types and counts
|
| 80 |
-
2. Include both correct and incorrect examples
|
| 81 |
-
3. Vary in complexity (trace length, agent interaction)
|
| 82 |
-
4. Cover different problem domains
|
| 83 |
-
"""
|
| 84 |
-
if len(traces) <= count:
|
| 85 |
-
return traces
|
| 86 |
-
|
| 87 |
-
# Categorize traces
|
| 88 |
-
categorized = {
|
| 89 |
-
'single_agent': [],
|
| 90 |
-
'multi_agent_simple': [], # 2-3 agents
|
| 91 |
-
'multi_agent_complex': [], # 4+ agents
|
| 92 |
-
'correct_examples': [],
|
| 93 |
-
'incorrect_examples': [],
|
| 94 |
-
'short_traces': [],
|
| 95 |
-
'medium_traces': [],
|
| 96 |
-
'long_traces': []
|
| 97 |
-
}
|
| 98 |
-
|
| 99 |
-
for trace in traces:
|
| 100 |
-
agents = trace.get('agents', [])
|
| 101 |
-
agent_count = len(agents) if agents else 1
|
| 102 |
-
is_correct = trace.get('is_correct', None)
|
| 103 |
-
trace_length = len(trace.get('trace', ''))
|
| 104 |
-
|
| 105 |
-
# Categorize by agent count
|
| 106 |
-
if agent_count == 1:
|
| 107 |
-
categorized['single_agent'].append(trace)
|
| 108 |
-
elif agent_count <= 3:
|
| 109 |
-
categorized['multi_agent_simple'].append(trace)
|
| 110 |
-
else:
|
| 111 |
-
categorized['multi_agent_complex'].append(trace)
|
| 112 |
-
|
| 113 |
-
# Categorize by correctness
|
| 114 |
-
if is_correct is True:
|
| 115 |
-
categorized['correct_examples'].append(trace)
|
| 116 |
-
elif is_correct is False:
|
| 117 |
-
categorized['incorrect_examples'].append(trace)
|
| 118 |
-
|
| 119 |
-
# Categorize by trace length
|
| 120 |
-
if trace_length < 2000:
|
| 121 |
-
categorized['short_traces'].append(trace)
|
| 122 |
-
elif trace_length < 8000:
|
| 123 |
-
categorized['medium_traces'].append(trace)
|
| 124 |
-
else:
|
| 125 |
-
categorized['long_traces'].append(trace)
|
| 126 |
-
|
| 127 |
-
# Smart selection to ensure diversity
|
| 128 |
-
selected = []
|
| 129 |
-
|
| 130 |
-
# Selection strategy: ensure we have examples from each important category
|
| 131 |
-
selection_plan = [
|
| 132 |
-
('single_agent', 1),
|
| 133 |
-
('multi_agent_simple', 2),
|
| 134 |
-
('multi_agent_complex', 2),
|
| 135 |
-
('correct_examples', 1),
|
| 136 |
-
('incorrect_examples', 2)
|
| 137 |
-
]
|
| 138 |
-
|
| 139 |
-
used_ids = set()
|
| 140 |
-
for category, target_count in selection_plan:
|
| 141 |
-
candidates = [t for t in categorized[category] if t['id'] not in used_ids]
|
| 142 |
-
selected_from_category = random.sample(
|
| 143 |
-
candidates,
|
| 144 |
-
min(target_count, len(candidates))
|
| 145 |
-
)
|
| 146 |
-
selected.extend(selected_from_category)
|
| 147 |
-
used_ids.update(t['id'] for t in selected_from_category)
|
| 148 |
-
|
| 149 |
-
# Fill remaining slots with random selections
|
| 150 |
-
remaining_slots = count - len(selected)
|
| 151 |
-
if remaining_slots > 0:
|
| 152 |
-
remaining_candidates = [t for t in traces if t['id'] not in used_ids]
|
| 153 |
-
additional = random.sample(
|
| 154 |
-
remaining_candidates,
|
| 155 |
-
min(remaining_slots, len(remaining_candidates))
|
| 156 |
-
)
|
| 157 |
-
selected.extend(additional)
|
| 158 |
-
|
| 159 |
-
logger.info(f"Selected {len(selected)} diverse samples from {len(traces)} total traces")
|
| 160 |
-
return selected[:count]
|
| 161 |
-
|
| 162 |
-
def preload_trace_to_db(self, trace_data: Dict[str, Any], db: Session) -> str:
|
| 163 |
-
"""
|
| 164 |
-
Preload a single trace into the database with rich metadata.
|
| 165 |
-
|
| 166 |
-
Returns:
|
| 167 |
-
trace_id of the created trace
|
| 168 |
-
"""
|
| 169 |
-
# Prepare enhanced metadata
|
| 170 |
-
agents = trace_data.get('agents', [])
|
| 171 |
-
agent_count = len(agents) if agents else 1
|
| 172 |
-
|
| 173 |
-
# Create descriptive title
|
| 174 |
-
question = trace_data.get('question', '')
|
| 175 |
-
title_prefix = f"Sample: {agent_count}-Agent"
|
| 176 |
-
if question:
|
| 177 |
-
# Truncate question for title
|
| 178 |
-
question_snippet = question[:60] + "..." if len(question) > 60 else question
|
| 179 |
-
title = f"{title_prefix} - {question_snippet}"
|
| 180 |
-
else:
|
| 181 |
-
title = f"{title_prefix} Example #{trace_data['id']}"
|
| 182 |
-
|
| 183 |
-
# Enhanced description
|
| 184 |
-
description_parts = []
|
| 185 |
-
if question:
|
| 186 |
-
description_parts.append(f"Question: {question}")
|
| 187 |
-
|
| 188 |
-
if agents:
|
| 189 |
-
description_parts.append(f"Agents: {', '.join(agents)}")
|
| 190 |
-
|
| 191 |
-
mistake_reason = trace_data.get('mistake_reason')
|
| 192 |
-
if mistake_reason:
|
| 193 |
-
description_parts.append(f"Analysis: {mistake_reason}")
|
| 194 |
-
|
| 195 |
-
description = " | ".join(description_parts)
|
| 196 |
-
|
| 197 |
-
# Rich tags for categorization and filtering
|
| 198 |
-
tags = [
|
| 199 |
-
"sample",
|
| 200 |
-
"preloaded",
|
| 201 |
-
trace_data.get('subset', '').lower().replace('-', '_'),
|
| 202 |
-
f"{agent_count}_agents"
|
| 203 |
-
]
|
| 204 |
-
|
| 205 |
-
if trace_data.get('is_correct') is True:
|
| 206 |
-
tags.append("correct_execution")
|
| 207 |
-
elif trace_data.get('is_correct') is False:
|
| 208 |
-
tags.append("contains_errors")
|
| 209 |
-
|
| 210 |
-
if agents:
|
| 211 |
-
# Add agent-specific tags
|
| 212 |
-
for agent in agents[:3]: # Limit to first 3 to avoid tag explosion
|
| 213 |
-
clean_agent = agent.replace('_', '').replace('-', '').lower()
|
| 214 |
-
tags.append(f"agent_{clean_agent}")
|
| 215 |
-
|
| 216 |
-
# Enhanced metadata
|
| 217 |
-
enhanced_metadata = {
|
| 218 |
-
"source": "example_dataset",
|
| 219 |
-
"original_id": trace_data['id'],
|
| 220 |
-
"subset": trace_data.get('subset'),
|
| 221 |
-
"question_id": trace_data.get('question_id'),
|
| 222 |
-
"ground_truth": trace_data.get('ground_truth'),
|
| 223 |
-
"mistake_step": trace_data.get('mistake_step'),
|
| 224 |
-
"mistake_agent": trace_data.get('mistake_agent'),
|
| 225 |
-
"agents": agents,
|
| 226 |
-
"agent_count": agent_count,
|
| 227 |
-
"is_correct": trace_data.get('is_correct'),
|
| 228 |
-
"preloaded": True,
|
| 229 |
-
"quality": "curated_sample"
|
| 230 |
-
}
|
| 231 |
-
|
| 232 |
-
# Save to database
|
| 233 |
-
trace = save_trace(
|
| 234 |
-
session=db,
|
| 235 |
-
content=trace_data['trace'],
|
| 236 |
-
filename=f"sample_{trace_data['subset'].lower().replace('-', '_')}_{trace_data['id']}.json",
|
| 237 |
-
title=title,
|
| 238 |
-
description=description[:500], # Limit description length
|
| 239 |
-
trace_type="sample",
|
| 240 |
-
trace_source="preloaded_example",
|
| 241 |
-
tags=tags,
|
| 242 |
-
trace_metadata=enhanced_metadata
|
| 243 |
-
)
|
| 244 |
-
|
| 245 |
-
logger.info(f"Preloaded trace: {title} (ID: {trace.trace_id})")
|
| 246 |
-
return trace.trace_id
|
| 247 |
-
|
| 248 |
-
def generate_knowledge_graph(self, trace_id: str, trace_content: str) -> bool:
|
| 249 |
-
"""
|
| 250 |
-
Generate knowledge graph for a preloaded trace.
|
| 251 |
-
|
| 252 |
-
Note: Knowledge graph generation is currently disabled for preload.
|
| 253 |
-
Users can generate knowledge graphs manually after the traces are loaded.
|
| 254 |
-
|
| 255 |
-
Returns:
|
| 256 |
-
True if successful, False otherwise
|
| 257 |
-
"""
|
| 258 |
-
logger.info(f"Knowledge graph generation for trace {trace_id} skipped (to be generated on-demand)")
|
| 259 |
-
# For now, we skip KG generation during preload to avoid complexity
|
| 260 |
-
# Users can generate KGs manually through the UI after traces are loaded
|
| 261 |
-
return False
|
| 262 |
-
|
| 263 |
-
def check_existing_preloaded_data(self, db: Session) -> bool:
|
| 264 |
-
"""Check if preloaded sample data already exists in database."""
|
| 265 |
-
try:
|
| 266 |
-
from backend.database import models
|
| 267 |
-
|
| 268 |
-
# Query for traces with preloaded tag
|
| 269 |
-
traces = db.query(models.Trace).filter(
|
| 270 |
-
models.Trace.trace_source == "preloaded_example"
|
| 271 |
-
).all()
|
| 272 |
-
|
| 273 |
-
return len(traces) > 0
|
| 274 |
-
|
| 275 |
-
except Exception as e:
|
| 276 |
-
logger.error(f"Error checking existing preloaded data: {e}")
|
| 277 |
-
return False
|
| 278 |
-
|
| 279 |
-
def preload_samples(self, count: int = 8, force: bool = False) -> Dict[str, Any]:
|
| 280 |
-
"""
|
| 281 |
-
Main method to preload sample traces and generate knowledge graphs.
|
| 282 |
-
|
| 283 |
-
Args:
|
| 284 |
-
count: Number of sample traces to preload
|
| 285 |
-
force: If True, preload even if samples already exist
|
| 286 |
-
|
| 287 |
-
Returns:
|
| 288 |
-
Summary of preloading results
|
| 289 |
-
"""
|
| 290 |
-
results = {
|
| 291 |
-
"success": False,
|
| 292 |
-
"traces_preloaded": 0,
|
| 293 |
-
"knowledge_graphs_generated": 0,
|
| 294 |
-
"errors": []
|
| 295 |
-
}
|
| 296 |
-
|
| 297 |
-
try:
|
| 298 |
-
# Initialize database
|
| 299 |
-
logger.info("Initializing database...")
|
| 300 |
-
init_database()
|
| 301 |
-
|
| 302 |
-
# Check if preloaded data already exists
|
| 303 |
-
with next(get_db()) as db:
|
| 304 |
-
if not force and self.check_existing_preloaded_data(db):
|
| 305 |
-
logger.info("Preloaded sample data already exists. Use --force to override.")
|
| 306 |
-
results["message"] = "Sample data already exists"
|
| 307 |
-
return results
|
| 308 |
-
|
| 309 |
-
# Load and select example traces
|
| 310 |
-
logger.info("Loading example traces...")
|
| 311 |
-
all_traces = self.load_example_traces()
|
| 312 |
-
|
| 313 |
-
if not all_traces:
|
| 314 |
-
results["errors"].append("No example traces found")
|
| 315 |
-
return results
|
| 316 |
-
|
| 317 |
-
# Select diverse samples
|
| 318 |
-
selected_traces = self.select_diverse_samples(all_traces, count)
|
| 319 |
-
logger.info(f"Selected {len(selected_traces)} traces for preloading")
|
| 320 |
-
|
| 321 |
-
# Preload traces to database
|
| 322 |
-
preloaded_trace_ids = []
|
| 323 |
-
for trace_data in selected_traces:
|
| 324 |
-
try:
|
| 325 |
-
trace_id = self.preload_trace_to_db(trace_data, db)
|
| 326 |
-
preloaded_trace_ids.append((trace_id, trace_data['trace']))
|
| 327 |
-
results["traces_preloaded"] += 1
|
| 328 |
-
|
| 329 |
-
except Exception as e:
|
| 330 |
-
error_msg = f"Failed to preload trace {trace_data['id']}: {e}"
|
| 331 |
-
logger.error(error_msg)
|
| 332 |
-
results["errors"].append(error_msg)
|
| 333 |
-
|
| 334 |
-
# Commit trace changes
|
| 335 |
-
db.commit()
|
| 336 |
-
|
| 337 |
-
# Generate knowledge graphs (outside of trace transaction)
|
| 338 |
-
kg_success_count = 0
|
| 339 |
-
for trace_id, trace_content in preloaded_trace_ids:
|
| 340 |
-
if self.generate_knowledge_graph(trace_id, trace_content):
|
| 341 |
-
kg_success_count += 1
|
| 342 |
-
|
| 343 |
-
results["knowledge_graphs_generated"] = kg_success_count
|
| 344 |
-
results["success"] = True
|
| 345 |
-
|
| 346 |
-
logger.info(f"""
|
| 347 |
-
Preloading completed successfully!
|
| 348 |
-
- Traces preloaded: {results['traces_preloaded']}
|
| 349 |
-
- Knowledge graphs generated: {results['knowledge_graphs_generated']}
|
| 350 |
-
- Errors: {len(results['errors'])}
|
| 351 |
-
""")
|
| 352 |
-
|
| 353 |
-
except Exception as e:
|
| 354 |
-
error_msg = f"Fatal error during preloading: {e}"
|
| 355 |
-
logger.error(error_msg)
|
| 356 |
-
results["errors"].append(error_msg)
|
| 357 |
-
|
| 358 |
-
return results
|
| 359 |
-
|
| 360 |
-
def main():
|
| 361 |
-
"""Parse arguments and run sample data preloading."""
|
| 362 |
-
parser = argparse.ArgumentParser(description='Preload sample traces and knowledge graphs')
|
| 363 |
-
parser.add_argument('--count', type=int, default=8,
|
| 364 |
-
help='Number of sample traces to preload (default: 8)')
|
| 365 |
-
parser.add_argument('--force', action='store_true',
|
| 366 |
-
help='Force preload even if sample data already exists')
|
| 367 |
-
parser.add_argument('--verbose', '-v', action='store_true',
|
| 368 |
-
help='Enable verbose logging')
|
| 369 |
-
|
| 370 |
-
args = parser.parse_args()
|
| 371 |
-
|
| 372 |
-
if args.verbose:
|
| 373 |
-
logging.getLogger().setLevel(logging.DEBUG)
|
| 374 |
-
|
| 375 |
-
# Run preloading
|
| 376 |
-
preloader = SampleDataPreloader()
|
| 377 |
-
results = preloader.preload_samples(count=args.count, force=args.force)
|
| 378 |
-
|
| 379 |
-
# Display results
|
| 380 |
-
if results["success"]:
|
| 381 |
-
print(f"✅ Successfully preloaded {results['traces_preloaded']} sample traces")
|
| 382 |
-
print(f"📊 Generated {results['knowledge_graphs_generated']} knowledge graphs")
|
| 383 |
-
if results["errors"]:
|
| 384 |
-
print(f"⚠️ {len(results['errors'])} errors occurred:")
|
| 385 |
-
for error in results["errors"]:
|
| 386 |
-
print(f" - {error}")
|
| 387 |
-
return 0
|
| 388 |
-
else:
|
| 389 |
-
print("❌ Preloading failed")
|
| 390 |
-
for error in results["errors"]:
|
| 391 |
-
print(f" - {error}")
|
| 392 |
-
return 1
|
| 393 |
-
|
| 394 |
-
if __name__ == "__main__":
|
| 395 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|