createsonline 0.1.26__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- createsonline/__init__.py +46 -0
- createsonline/admin/__init__.py +7 -0
- createsonline/admin/content.py +526 -0
- createsonline/admin/crud.py +805 -0
- createsonline/admin/field_builder.py +559 -0
- createsonline/admin/integration.py +482 -0
- createsonline/admin/interface.py +2562 -0
- createsonline/admin/model_creator.py +513 -0
- createsonline/admin/model_manager.py +388 -0
- createsonline/admin/modern_dashboard.py +498 -0
- createsonline/admin/permissions.py +264 -0
- createsonline/admin/user_forms.py +594 -0
- createsonline/ai/__init__.py +202 -0
- createsonline/ai/fields.py +1226 -0
- createsonline/ai/orm.py +325 -0
- createsonline/ai/services.py +1244 -0
- createsonline/app.py +506 -0
- createsonline/auth/__init__.py +8 -0
- createsonline/auth/management.py +228 -0
- createsonline/auth/models.py +552 -0
- createsonline/cli/__init__.py +5 -0
- createsonline/cli/commands/__init__.py +122 -0
- createsonline/cli/commands/database.py +416 -0
- createsonline/cli/commands/info.py +173 -0
- createsonline/cli/commands/initdb.py +218 -0
- createsonline/cli/commands/project.py +545 -0
- createsonline/cli/commands/serve.py +173 -0
- createsonline/cli/commands/shell.py +93 -0
- createsonline/cli/commands/users.py +148 -0
- createsonline/cli/main.py +2041 -0
- createsonline/cli/manage.py +274 -0
- createsonline/config/__init__.py +9 -0
- createsonline/config/app.py +2577 -0
- createsonline/config/database.py +179 -0
- createsonline/config/docs.py +384 -0
- createsonline/config/errors.py +160 -0
- createsonline/config/orm.py +43 -0
- createsonline/config/request.py +93 -0
- createsonline/config/settings.py +176 -0
- createsonline/data/__init__.py +23 -0
- createsonline/data/dataframe.py +925 -0
- createsonline/data/io.py +453 -0
- createsonline/data/series.py +557 -0
- createsonline/database/__init__.py +60 -0
- createsonline/database/abstraction.py +440 -0
- createsonline/database/assistant.py +585 -0
- createsonline/database/fields.py +442 -0
- createsonline/database/migrations.py +132 -0
- createsonline/database/models.py +604 -0
- createsonline/database.py +438 -0
- createsonline/http/__init__.py +28 -0
- createsonline/http/client.py +535 -0
- createsonline/ml/__init__.py +55 -0
- createsonline/ml/classification.py +552 -0
- createsonline/ml/clustering.py +680 -0
- createsonline/ml/metrics.py +542 -0
- createsonline/ml/neural.py +560 -0
- createsonline/ml/preprocessing.py +784 -0
- createsonline/ml/regression.py +501 -0
- createsonline/performance/__init__.py +19 -0
- createsonline/performance/cache.py +444 -0
- createsonline/performance/compression.py +335 -0
- createsonline/performance/core.py +419 -0
- createsonline/project_init.py +789 -0
- createsonline/routing.py +528 -0
- createsonline/security/__init__.py +34 -0
- createsonline/security/core.py +811 -0
- createsonline/security/encryption.py +349 -0
- createsonline/server.py +295 -0
- createsonline/static/css/admin.css +263 -0
- createsonline/static/css/common.css +358 -0
- createsonline/static/css/dashboard.css +89 -0
- createsonline/static/favicon.ico +0 -0
- createsonline/static/icons/icon-128x128.png +0 -0
- createsonline/static/icons/icon-128x128.webp +0 -0
- createsonline/static/icons/icon-16x16.png +0 -0
- createsonline/static/icons/icon-16x16.webp +0 -0
- createsonline/static/icons/icon-180x180.png +0 -0
- createsonline/static/icons/icon-180x180.webp +0 -0
- createsonline/static/icons/icon-192x192.png +0 -0
- createsonline/static/icons/icon-192x192.webp +0 -0
- createsonline/static/icons/icon-256x256.png +0 -0
- createsonline/static/icons/icon-256x256.webp +0 -0
- createsonline/static/icons/icon-32x32.png +0 -0
- createsonline/static/icons/icon-32x32.webp +0 -0
- createsonline/static/icons/icon-384x384.png +0 -0
- createsonline/static/icons/icon-384x384.webp +0 -0
- createsonline/static/icons/icon-48x48.png +0 -0
- createsonline/static/icons/icon-48x48.webp +0 -0
- createsonline/static/icons/icon-512x512.png +0 -0
- createsonline/static/icons/icon-512x512.webp +0 -0
- createsonline/static/icons/icon-64x64.png +0 -0
- createsonline/static/icons/icon-64x64.webp +0 -0
- createsonline/static/image/android-chrome-192x192.png +0 -0
- createsonline/static/image/android-chrome-512x512.png +0 -0
- createsonline/static/image/apple-touch-icon.png +0 -0
- createsonline/static/image/favicon-16x16.png +0 -0
- createsonline/static/image/favicon-32x32.png +0 -0
- createsonline/static/image/favicon.ico +0 -0
- createsonline/static/image/favicon.svg +17 -0
- createsonline/static/image/icon-128x128.png +0 -0
- createsonline/static/image/icon-128x128.webp +0 -0
- createsonline/static/image/icon-16x16.png +0 -0
- createsonline/static/image/icon-16x16.webp +0 -0
- createsonline/static/image/icon-180x180.png +0 -0
- createsonline/static/image/icon-180x180.webp +0 -0
- createsonline/static/image/icon-192x192.png +0 -0
- createsonline/static/image/icon-192x192.webp +0 -0
- createsonline/static/image/icon-256x256.png +0 -0
- createsonline/static/image/icon-256x256.webp +0 -0
- createsonline/static/image/icon-32x32.png +0 -0
- createsonline/static/image/icon-32x32.webp +0 -0
- createsonline/static/image/icon-384x384.png +0 -0
- createsonline/static/image/icon-384x384.webp +0 -0
- createsonline/static/image/icon-48x48.png +0 -0
- createsonline/static/image/icon-48x48.webp +0 -0
- createsonline/static/image/icon-512x512.png +0 -0
- createsonline/static/image/icon-512x512.webp +0 -0
- createsonline/static/image/icon-64x64.png +0 -0
- createsonline/static/image/icon-64x64.webp +0 -0
- createsonline/static/image/logo-header-h100.png +0 -0
- createsonline/static/image/logo-header-h100.webp +0 -0
- createsonline/static/image/logo-header-h200@2x.png +0 -0
- createsonline/static/image/logo-header-h200@2x.webp +0 -0
- createsonline/static/image/logo.png +0 -0
- createsonline/static/js/admin.js +274 -0
- createsonline/static/site.webmanifest +35 -0
- createsonline/static/templates/admin/base.html +87 -0
- createsonline/static/templates/admin/dashboard.html +217 -0
- createsonline/static/templates/admin/model_form.html +270 -0
- createsonline/static/templates/admin/model_list.html +202 -0
- createsonline/static/test_script.js +15 -0
- createsonline/static/test_styles.css +59 -0
- createsonline/static_files.py +365 -0
- createsonline/templates/404.html +100 -0
- createsonline/templates/admin_login.html +169 -0
- createsonline/templates/base.html +102 -0
- createsonline/templates/index.html +151 -0
- createsonline/templates.py +205 -0
- createsonline/testing.py +322 -0
- createsonline/utils.py +448 -0
- createsonline/validation/__init__.py +49 -0
- createsonline/validation/fields.py +598 -0
- createsonline/validation/models.py +504 -0
- createsonline/validation/validators.py +561 -0
- createsonline/views.py +184 -0
- createsonline-0.1.26.dist-info/METADATA +46 -0
- createsonline-0.1.26.dist-info/RECORD +152 -0
- createsonline-0.1.26.dist-info/WHEEL +5 -0
- createsonline-0.1.26.dist-info/entry_points.txt +2 -0
- createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
- createsonline-0.1.26.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1226 @@
|
|
|
1
|
+
# createsonline/ai/fields.py
|
|
2
|
+
"""
|
|
3
|
+
CREATESONLINE AI Field Types - COMPLETE INTERNAL IMPLEMENTATION
|
|
4
|
+
|
|
5
|
+
Intelligent field types that automatically compute values using AI models,
|
|
6
|
+
generate content with LLMs, and store vector embeddings for similarity search.
|
|
7
|
+
"""
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import math
|
|
12
|
+
import random
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
# Internal fallbacks for external dependencies
|
|
17
|
+
try:
|
|
18
|
+
import sqlalchemy as sa
|
|
19
|
+
from sqlalchemy import Column, Float, Integer, String, Text, JSON
|
|
20
|
+
from sqlalchemy.types import TypeDecorator, UserDefinedType
|
|
21
|
+
SA_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
SA_AVAILABLE = False
|
|
24
|
+
# Create mock SQLAlchemy components
|
|
25
|
+
class MockSQLAlchemy:
|
|
26
|
+
Float = "Float"
|
|
27
|
+
Integer = "Integer"
|
|
28
|
+
String = lambda x: f"String({x})"
|
|
29
|
+
Text = "Text"
|
|
30
|
+
JSON = "JSON"
|
|
31
|
+
Column = lambda *args, **kwargs: f"Column({args}, {kwargs})"
|
|
32
|
+
sa = MockSQLAlchemy()
|
|
33
|
+
|
|
34
|
+
# ========================================
|
|
35
|
+
# INTERNAL AI SERVICES
|
|
36
|
+
# ========================================
|
|
37
|
+
|
|
38
|
+
class InternalAIEngine:
|
|
39
|
+
"""Pure Python AI engine with mock/basic implementations"""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.cache = {}
|
|
43
|
+
self.models = {}
|
|
44
|
+
|
|
45
|
+
def hash_text(self, text: str) -> str:
|
|
46
|
+
"""Generate consistent hash for text"""
|
|
47
|
+
return hashlib.md5(text.encode()).hexdigest()
|
|
48
|
+
|
|
49
|
+
def generate_embedding(self, text: str, dimensions: int = 768) -> List[float]:
|
|
50
|
+
"""Generate consistent mock embedding from text"""
|
|
51
|
+
hash_val = self.hash_text(text)
|
|
52
|
+
|
|
53
|
+
# Use hash to seed deterministic "embedding"
|
|
54
|
+
embedding = []
|
|
55
|
+
for i in range(dimensions):
|
|
56
|
+
# Create pseudo-random but deterministic values
|
|
57
|
+
seed_char = hash_val[i % len(hash_val)]
|
|
58
|
+
seed_value = ord(seed_char) / 255.0 # Normalize to [0,1]
|
|
59
|
+
|
|
60
|
+
# Apply some math to spread values
|
|
61
|
+
value = math.sin(seed_value * math.pi * 2) * 0.5
|
|
62
|
+
embedding.append(value)
|
|
63
|
+
|
|
64
|
+
return embedding
|
|
65
|
+
|
|
66
|
+
def similarity(self, vec1: List[float], vec2: List[float]) -> float:
|
|
67
|
+
"""Calculate cosine similarity"""
|
|
68
|
+
try:
|
|
69
|
+
# Dot product
|
|
70
|
+
dot_product = sum(a * b for a, b in zip(vec1, vec2))
|
|
71
|
+
|
|
72
|
+
# Magnitudes
|
|
73
|
+
mag1 = math.sqrt(sum(a * a for a in vec1))
|
|
74
|
+
mag2 = math.sqrt(sum(b * b for b in vec2))
|
|
75
|
+
|
|
76
|
+
if mag1 == 0 or mag2 == 0:
|
|
77
|
+
return 0.0
|
|
78
|
+
|
|
79
|
+
return dot_product / (mag1 * mag2)
|
|
80
|
+
except:
|
|
81
|
+
return 0.0
|
|
82
|
+
|
|
83
|
+
def generate_text(self, prompt: str, max_tokens: int = 100) -> str:
|
|
84
|
+
"""Generate mock text based on prompt"""
|
|
85
|
+
prompt_words = prompt.lower().split()
|
|
86
|
+
|
|
87
|
+
# Simple rule-based generation
|
|
88
|
+
if any(word in prompt_words for word in ['summary', 'summarize']):
|
|
89
|
+
return f"Summary: {prompt[:50]}... (Generated by CREATESONLINE AI)"
|
|
90
|
+
elif any(word in prompt_words for word in ['title', 'headline']):
|
|
91
|
+
return f"Title: {prompt.split()[-3:]} - AI Generated"
|
|
92
|
+
elif any(word in prompt_words for word in ['description', 'explain']):
|
|
93
|
+
return f"This is an AI-generated description based on: {prompt[:40]}..."
|
|
94
|
+
else:
|
|
95
|
+
return f"AI Response: Generated content for '{prompt[:30]}...' using CREATESONLINE"
|
|
96
|
+
|
|
97
|
+
def classify_text(self, text: str, categories: List[str] = None) -> Dict[str, float]:
|
|
98
|
+
"""Basic text classification"""
|
|
99
|
+
if not categories:
|
|
100
|
+
categories = ["positive", "negative", "neutral"]
|
|
101
|
+
|
|
102
|
+
text_lower = text.lower()
|
|
103
|
+
scores = {}
|
|
104
|
+
|
|
105
|
+
# Simple keyword-based classification
|
|
106
|
+
positive_words = {"good", "great", "excellent", "amazing", "wonderful", "fantastic"}
|
|
107
|
+
negative_words = {"bad", "terrible", "awful", "horrible", "disappointing", "poor"}
|
|
108
|
+
|
|
109
|
+
positive_count = sum(1 for word in positive_words if word in text_lower)
|
|
110
|
+
negative_count = sum(1 for word in negative_words if word in text_lower)
|
|
111
|
+
|
|
112
|
+
if "positive" in categories:
|
|
113
|
+
scores["positive"] = min(1.0, positive_count * 0.3 + 0.1)
|
|
114
|
+
if "negative" in categories:
|
|
115
|
+
scores["negative"] = min(1.0, negative_count * 0.3 + 0.1)
|
|
116
|
+
if "neutral" in categories:
|
|
117
|
+
scores["neutral"] = 1.0 - max(scores.get("positive", 0), scores.get("negative", 0))
|
|
118
|
+
|
|
119
|
+
return scores
|
|
120
|
+
|
|
121
|
+
def predict_numeric(self, features: Dict[str, Any]) -> float:
|
|
122
|
+
"""Mock numeric prediction"""
|
|
123
|
+
# Simple linear combination with some randomness
|
|
124
|
+
feature_sum = 0
|
|
125
|
+
for key, value in features.items():
|
|
126
|
+
if isinstance(value, (int, float)):
|
|
127
|
+
feature_sum += value
|
|
128
|
+
elif isinstance(value, str):
|
|
129
|
+
feature_sum += len(value) * 0.1
|
|
130
|
+
|
|
131
|
+
# Add some deterministic "AI magic"
|
|
132
|
+
hash_val = self.hash_text(str(features))
|
|
133
|
+
seed = int(hash_val[:8], 16) % 100
|
|
134
|
+
prediction = (feature_sum * 0.1 + seed * 0.01) % 1.0
|
|
135
|
+
|
|
136
|
+
return prediction
|
|
137
|
+
|
|
138
|
+
# Global AI engine instance
|
|
139
|
+
_ai_engine = InternalAIEngine()
|
|
140
|
+
|
|
141
|
+
# ========================================
|
|
142
|
+
# BASE AI FIELD FUNCTIONALITY
|
|
143
|
+
# ========================================
|
|
144
|
+
|
|
145
|
+
class AIFieldMixin:
|
|
146
|
+
"""Base mixin for AI-enhanced fields with internal implementations"""
|
|
147
|
+
|
|
148
|
+
def __init__(self, ai_config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
149
|
+
"""Initialize AI field with configuration"""
|
|
150
|
+
self.ai_config = ai_config or {}
|
|
151
|
+
self.ai_enabled = True
|
|
152
|
+
self.ai_cache_enabled = True
|
|
153
|
+
self.ai_cache_ttl = 3600 # 1 hour
|
|
154
|
+
self._ai_cache = {}
|
|
155
|
+
|
|
156
|
+
# Default AI configuration
|
|
157
|
+
self._setup_ai_defaults()
|
|
158
|
+
|
|
159
|
+
def _setup_ai_defaults(self):
|
|
160
|
+
"""Setup default AI configuration"""
|
|
161
|
+
defaults = {
|
|
162
|
+
"enable_caching": True,
|
|
163
|
+
"cache_ttl": 3600,
|
|
164
|
+
"fallback_value": None,
|
|
165
|
+
"confidence_threshold": 0.5,
|
|
166
|
+
"mock_mode": True, # Use internal implementations by default
|
|
167
|
+
"provider": "internal"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for key, value in defaults.items():
|
|
171
|
+
if key not in self.ai_config:
|
|
172
|
+
self.ai_config[key] = value
|
|
173
|
+
|
|
174
|
+
def get_ai_service(self, service_type: str = "internal"):
|
|
175
|
+
"""Get AI service - always returns internal engine"""
|
|
176
|
+
return _ai_engine
|
|
177
|
+
|
|
178
|
+
def generate_cache_key(self, input_data: Any) -> str:
|
|
179
|
+
"""Generate cache key for AI operations"""
|
|
180
|
+
if isinstance(input_data, (dict, list)):
|
|
181
|
+
input_str = json.dumps(input_data, sort_keys=True, default=str)
|
|
182
|
+
else:
|
|
183
|
+
input_str = str(input_data)
|
|
184
|
+
|
|
185
|
+
cache_data = {
|
|
186
|
+
"input": input_str,
|
|
187
|
+
"config": {k: v for k, v in self.ai_config.items() if k != "api_key"},
|
|
188
|
+
"field_type": self.__class__.__name__
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cache_str = json.dumps(cache_data, sort_keys=True)
|
|
192
|
+
return hashlib.md5(cache_str.encode()).hexdigest()
|
|
193
|
+
|
|
194
|
+
def is_cache_valid(self, cache_key: str) -> bool:
|
|
195
|
+
"""Check if cached result is still valid"""
|
|
196
|
+
if not self.ai_cache_enabled or cache_key not in self._ai_cache:
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
cached_time = self._ai_cache[cache_key].get("timestamp", 0)
|
|
200
|
+
return (datetime.utcnow().timestamp() - cached_time) < self.ai_cache_ttl
|
|
201
|
+
|
|
202
|
+
def get_cached_result(self, cache_key: str) -> Any:
|
|
203
|
+
"""Get cached AI result"""
|
|
204
|
+
if self.is_cache_valid(cache_key):
|
|
205
|
+
return self._ai_cache[cache_key]["result"]
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
def set_cached_result(self, cache_key: str, result: Any):
|
|
209
|
+
"""Cache AI result"""
|
|
210
|
+
if self.ai_cache_enabled:
|
|
211
|
+
self._ai_cache[cache_key] = {
|
|
212
|
+
"result": result,
|
|
213
|
+
"timestamp": datetime.utcnow().timestamp()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# ========================================
|
|
217
|
+
# AI FIELD UTILITIES AND HELPERS
|
|
218
|
+
# ========================================
|
|
219
|
+
|
|
220
|
+
class AIFieldManager:
|
|
221
|
+
"""Manager for AI field operations across models"""
|
|
222
|
+
|
|
223
|
+
def __init__(self):
|
|
224
|
+
self.registered_fields = {}
|
|
225
|
+
self.compute_queue = []
|
|
226
|
+
self.stats = {
|
|
227
|
+
"computations": 0,
|
|
228
|
+
"cache_hits": 0,
|
|
229
|
+
"cache_misses": 0,
|
|
230
|
+
"errors": 0
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
def register_field(self, model_class, field_name, field_instance):
|
|
234
|
+
"""Register an AI field for management"""
|
|
235
|
+
model_key = f"{model_class.__module__}.{model_class.__name__}"
|
|
236
|
+
if model_key not in self.registered_fields:
|
|
237
|
+
self.registered_fields[model_key] = {}
|
|
238
|
+
|
|
239
|
+
self.registered_fields[model_key][field_name] = field_instance
|
|
240
|
+
|
|
241
|
+
async def compute_all_fields(self, instance):
|
|
242
|
+
"""Compute all AI fields for a model instance"""
|
|
243
|
+
model_key = f"{instance.__class__.__module__}.{instance.__class__.__name__}"
|
|
244
|
+
|
|
245
|
+
if model_key not in self.registered_fields:
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
for field_name, field_instance in self.registered_fields[model_key].items():
|
|
249
|
+
try:
|
|
250
|
+
await self._compute_field(instance, field_name, field_instance)
|
|
251
|
+
self.stats["computations"] += 1
|
|
252
|
+
except Exception as e:
|
|
253
|
+
self.stats["errors"] += 1
|
|
254
|
+
|
|
255
|
+
async def _compute_field(self, instance, field_name, field_instance):
|
|
256
|
+
"""Compute a specific AI field"""
|
|
257
|
+
if isinstance(field_instance, AIComputedField):
|
|
258
|
+
features = self._extract_features(instance, field_instance)
|
|
259
|
+
value = await field_instance.compute_value(instance, features)
|
|
260
|
+
setattr(instance, field_name, value)
|
|
261
|
+
|
|
262
|
+
elif isinstance(field_instance, LLMField):
|
|
263
|
+
template_data = self._extract_template_data(instance, field_instance)
|
|
264
|
+
content = await field_instance.generate_content(instance, template_data)
|
|
265
|
+
setattr(instance, field_name, content)
|
|
266
|
+
|
|
267
|
+
elif isinstance(field_instance, VectorField):
|
|
268
|
+
source_data = self._extract_source_data(instance, field_instance)
|
|
269
|
+
embedding = await field_instance.generate_embedding(instance, source_data)
|
|
270
|
+
setattr(instance, field_name, embedding)
|
|
271
|
+
|
|
272
|
+
elif isinstance(field_instance, SmartTextField):
|
|
273
|
+
text_value = getattr(instance, field_name, "")
|
|
274
|
+
if text_value:
|
|
275
|
+
analysis = await field_instance.analyze_text(text_value)
|
|
276
|
+
# Store analysis in a related field or instance metadata
|
|
277
|
+
setattr(instance, f"{field_name}_analysis", analysis)
|
|
278
|
+
|
|
279
|
+
def _extract_features(self, instance, field_instance):
|
|
280
|
+
"""Extract features for AI computation"""
|
|
281
|
+
features = {}
|
|
282
|
+
feature_fields = field_instance.ai_config.get('features', [])
|
|
283
|
+
source_field = field_instance.ai_config.get('source_field')
|
|
284
|
+
|
|
285
|
+
if feature_fields:
|
|
286
|
+
for feature_field in feature_fields:
|
|
287
|
+
if hasattr(instance, feature_field):
|
|
288
|
+
features[feature_field] = getattr(instance, feature_field)
|
|
289
|
+
elif source_field:
|
|
290
|
+
if hasattr(instance, source_field):
|
|
291
|
+
features[source_field] = getattr(instance, source_field)
|
|
292
|
+
else:
|
|
293
|
+
# Extract all non-AI fields as features
|
|
294
|
+
for attr_name in dir(instance):
|
|
295
|
+
if not attr_name.startswith('_') and hasattr(instance, attr_name):
|
|
296
|
+
value = getattr(instance, attr_name)
|
|
297
|
+
if isinstance(value, (str, int, float, bool)) and value is not None:
|
|
298
|
+
features[attr_name] = value
|
|
299
|
+
|
|
300
|
+
return features
|
|
301
|
+
|
|
302
|
+
def _extract_template_data(self, instance, field_instance):
|
|
303
|
+
"""Extract template data for LLM generation"""
|
|
304
|
+
template_data = {}
|
|
305
|
+
|
|
306
|
+
# Extract all suitable fields as template variables
|
|
307
|
+
for attr_name in dir(instance):
|
|
308
|
+
if not attr_name.startswith('_') and hasattr(instance, attr_name):
|
|
309
|
+
value = getattr(instance, attr_name)
|
|
310
|
+
if isinstance(value, (str, int, float, bool)) and value is not None:
|
|
311
|
+
template_data[attr_name] = value
|
|
312
|
+
|
|
313
|
+
return template_data
|
|
314
|
+
|
|
315
|
+
def _extract_source_data(self, instance, field_instance):
|
|
316
|
+
"""Extract source data for vector embedding"""
|
|
317
|
+
source_field = field_instance.ai_config.get('source_field')
|
|
318
|
+
|
|
319
|
+
if source_field and hasattr(instance, source_field):
|
|
320
|
+
return getattr(instance, source_field)
|
|
321
|
+
|
|
322
|
+
# Fallback to common text fields
|
|
323
|
+
for fallback_field in ['description', 'content', 'text', 'name', 'title']:
|
|
324
|
+
if hasattr(instance, fallback_field):
|
|
325
|
+
value = getattr(instance, fallback_field)
|
|
326
|
+
if value:
|
|
327
|
+
return value
|
|
328
|
+
|
|
329
|
+
return str(instance)
|
|
330
|
+
|
|
331
|
+
def get_stats(self):
|
|
332
|
+
"""Get AI field computation statistics"""
|
|
333
|
+
return self.stats.copy()
|
|
334
|
+
|
|
335
|
+
# Global AI field manager
|
|
336
|
+
_ai_field_manager = AIFieldManager()
|
|
337
|
+
|
|
338
|
+
def get_ai_field_manager():
|
|
339
|
+
"""Get the global AI field manager"""
|
|
340
|
+
return _ai_field_manager
|
|
341
|
+
|
|
342
|
+
# ========================================
|
|
343
|
+
# MODEL INTEGRATION HELPERS
|
|
344
|
+
# ========================================
|
|
345
|
+
|
|
346
|
+
class AIModelMixin:
|
|
347
|
+
"""Mixin for models that use AI fields"""
|
|
348
|
+
|
|
349
|
+
def __init__(self, *args, **kwargs):
|
|
350
|
+
super().__init__(*args, **kwargs)
|
|
351
|
+
self._register_ai_fields()
|
|
352
|
+
|
|
353
|
+
def _register_ai_fields(self):
|
|
354
|
+
"""Register AI fields with the field manager"""
|
|
355
|
+
for attr_name in dir(self.__class__):
|
|
356
|
+
attr = getattr(self.__class__, attr_name)
|
|
357
|
+
if isinstance(attr, AIFieldMixin):
|
|
358
|
+
_ai_field_manager.register_field(self.__class__, attr_name, attr)
|
|
359
|
+
|
|
360
|
+
async def compute_ai_fields(self):
|
|
361
|
+
"""Compute all AI fields for this instance"""
|
|
362
|
+
await _ai_field_manager.compute_all_fields(self)
|
|
363
|
+
|
|
364
|
+
async def compute_field(self, field_name):
|
|
365
|
+
"""Compute a specific AI field"""
|
|
366
|
+
field_instance = getattr(self.__class__, field_name, None)
|
|
367
|
+
if field_instance and isinstance(field_instance, AIFieldMixin):
|
|
368
|
+
await _ai_field_manager._compute_field(self, field_name, field_instance)
|
|
369
|
+
|
|
370
|
+
# ========================================
|
|
371
|
+
# VALIDATION AND TYPE CHECKING
|
|
372
|
+
# ========================================
|
|
373
|
+
|
|
374
|
+
def validate_ai_config(config: Dict[str, Any], field_type: str) -> Dict[str, Any]:
|
|
375
|
+
"""Validate and normalize AI configuration"""
|
|
376
|
+
|
|
377
|
+
# Common validation
|
|
378
|
+
validated = config.copy()
|
|
379
|
+
|
|
380
|
+
# Ensure required fields exist
|
|
381
|
+
if "provider" not in validated:
|
|
382
|
+
validated["provider"] = "internal"
|
|
383
|
+
|
|
384
|
+
if "enable_caching" not in validated:
|
|
385
|
+
validated["enable_caching"] = True
|
|
386
|
+
|
|
387
|
+
# Field-specific validation
|
|
388
|
+
if field_type == "AIComputedField":
|
|
389
|
+
if "prediction_type" not in validated:
|
|
390
|
+
validated["prediction_type"] = "regression"
|
|
391
|
+
|
|
392
|
+
if validated["prediction_type"] not in ["classification", "regression"]:
|
|
393
|
+
validated["prediction_type"] = "regression"
|
|
394
|
+
|
|
395
|
+
elif field_type == "LLMField":
|
|
396
|
+
if "max_tokens" not in validated:
|
|
397
|
+
validated["max_tokens"] = 500
|
|
398
|
+
|
|
399
|
+
if "temperature" not in validated:
|
|
400
|
+
validated["temperature"] = 0.7
|
|
401
|
+
|
|
402
|
+
# Ensure temperature is in valid range
|
|
403
|
+
validated["temperature"] = max(0.0, min(2.0, validated["temperature"]))
|
|
404
|
+
|
|
405
|
+
elif field_type == "VectorField":
|
|
406
|
+
if "similarity_metric" not in validated:
|
|
407
|
+
validated["similarity_metric"] = "cosine"
|
|
408
|
+
|
|
409
|
+
if validated["similarity_metric"] not in ["cosine", "euclidean", "dot_product"]:
|
|
410
|
+
validated["similarity_metric"] = "cosine"
|
|
411
|
+
|
|
412
|
+
return validated
|
|
413
|
+
|
|
414
|
+
def check_ai_field_compatibility(field_instance, model_class):
|
|
415
|
+
"""Check if AI field is compatible with model"""
|
|
416
|
+
|
|
417
|
+
issues = []
|
|
418
|
+
|
|
419
|
+
# Check if model has required source fields
|
|
420
|
+
if hasattr(field_instance, 'ai_config'):
|
|
421
|
+
source_field = field_instance.ai_config.get('source_field')
|
|
422
|
+
if source_field and not hasattr(model_class, source_field):
|
|
423
|
+
issues.append(f"Source field '{source_field}' not found in model {model_class.__name__}")
|
|
424
|
+
|
|
425
|
+
features = field_instance.ai_config.get('features', [])
|
|
426
|
+
for feature in features:
|
|
427
|
+
if not hasattr(model_class, feature):
|
|
428
|
+
issues.append(f"Feature field '{feature}' not found in model {model_class.__name__}")
|
|
429
|
+
|
|
430
|
+
return issues
|
|
431
|
+
|
|
432
|
+
# ========================================
|
|
433
|
+
# CREATESONLINE AI FIELD TYPES
|
|
434
|
+
# ========================================
|
|
435
|
+
|
|
436
|
+
class CreatesonlineField:
|
|
437
|
+
"""Base CREATESONLINE field that works with or without SQLAlchemy"""
|
|
438
|
+
|
|
439
|
+
def __init__(self, field_type=None, *args, **kwargs):
|
|
440
|
+
self.field_type = field_type or "Text"
|
|
441
|
+
self.args = args
|
|
442
|
+
self.kwargs = kwargs
|
|
443
|
+
self.name = None
|
|
444
|
+
self.value = None
|
|
445
|
+
|
|
446
|
+
def __set_name__(self, owner, name):
|
|
447
|
+
"""Called when field is assigned to a class"""
|
|
448
|
+
self.name = name
|
|
449
|
+
|
|
450
|
+
def __get__(self, instance, owner):
|
|
451
|
+
"""Get field value"""
|
|
452
|
+
if instance is None:
|
|
453
|
+
return self
|
|
454
|
+
return getattr(instance, f"_{self.name}", self.value)
|
|
455
|
+
|
|
456
|
+
def __set__(self, instance, value):
|
|
457
|
+
"""Set field value"""
|
|
458
|
+
setattr(instance, f"_{self.name}", value)
|
|
459
|
+
|
|
460
|
+
class AIComputedField(CreatesonlineField, AIFieldMixin):
|
|
461
|
+
"""
|
|
462
|
+
Field that automatically computes values using AI models
|
|
463
|
+
|
|
464
|
+
Examples:
|
|
465
|
+
# Predict customer lifetime value
|
|
466
|
+
clv = AIComputedField("Float", ai_config={
|
|
467
|
+
"model": "customer_lifetime_value",
|
|
468
|
+
"features": ["age", "purchase_history", "engagement_score"],
|
|
469
|
+
"prediction_type": "regression"
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
# Classify content category
|
|
473
|
+
category = AIComputedField("String", ai_config={
|
|
474
|
+
"model": "content_classifier",
|
|
475
|
+
"source_field": "description",
|
|
476
|
+
"prediction_type": "classification"
|
|
477
|
+
})
|
|
478
|
+
"""
|
|
479
|
+
|
|
480
|
+
def __init__(self, field_type="Float", *args, ai_config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
481
|
+
"""Initialize AI computed field"""
|
|
482
|
+
|
|
483
|
+
# Default AI configuration for computed fields
|
|
484
|
+
default_config = {
|
|
485
|
+
"model": "default_predictor",
|
|
486
|
+
"prediction_type": "regression",
|
|
487
|
+
"confidence_threshold": 0.5,
|
|
488
|
+
"fallback_value": 0.0,
|
|
489
|
+
"enable_async": True,
|
|
490
|
+
"timeout": 30,
|
|
491
|
+
"features": [],
|
|
492
|
+
"source_field": None
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
ai_config = {**default_config, **(ai_config or {})}
|
|
496
|
+
|
|
497
|
+
# Initialize base classes
|
|
498
|
+
CreatesonlineField.__init__(self, field_type, *args, **kwargs)
|
|
499
|
+
AIFieldMixin.__init__(self, ai_config=ai_config)
|
|
500
|
+
|
|
501
|
+
# SQLAlchemy integration if available
|
|
502
|
+
if SA_AVAILABLE and field_type in ["Float", "Integer", "String", "Text"]:
|
|
503
|
+
try:
|
|
504
|
+
if field_type == "Float":
|
|
505
|
+
self.sql_field = sa.Column(sa.Float, *args, **kwargs)
|
|
506
|
+
elif field_type == "Integer":
|
|
507
|
+
self.sql_field = sa.Column(sa.Integer, *args, **kwargs)
|
|
508
|
+
elif field_type == "String":
|
|
509
|
+
length = kwargs.get("length", 255)
|
|
510
|
+
self.sql_field = sa.Column(sa.String(length), *args, **kwargs)
|
|
511
|
+
elif field_type == "Text":
|
|
512
|
+
self.sql_field = sa.Column(sa.Text, *args, **kwargs)
|
|
513
|
+
except:
|
|
514
|
+
self.sql_field = None
|
|
515
|
+
|
|
516
|
+
async def compute_value(self, instance, features_data: Dict[str, Any]) -> Any:
|
|
517
|
+
"""Compute field value using AI model"""
|
|
518
|
+
if not self.ai_enabled:
|
|
519
|
+
return self.ai_config.get("fallback_value")
|
|
520
|
+
|
|
521
|
+
# Generate cache key
|
|
522
|
+
cache_key = self.generate_cache_key(features_data)
|
|
523
|
+
|
|
524
|
+
# Check cache first
|
|
525
|
+
cached_result = self.get_cached_result(cache_key)
|
|
526
|
+
if cached_result is not None:
|
|
527
|
+
return cached_result
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
# Get AI service
|
|
531
|
+
ai_service = self.get_ai_service()
|
|
532
|
+
|
|
533
|
+
# Prepare features
|
|
534
|
+
processed_features = await self._preprocess_features(features_data)
|
|
535
|
+
|
|
536
|
+
# Make prediction based on type
|
|
537
|
+
if self.ai_config["prediction_type"] == "classification":
|
|
538
|
+
categories = self.ai_config.get("categories", ["positive", "negative", "neutral"])
|
|
539
|
+
prediction_result = ai_service.classify_text(
|
|
540
|
+
str(processed_features.get("text", processed_features)),
|
|
541
|
+
categories
|
|
542
|
+
)
|
|
543
|
+
# Return the category with highest score
|
|
544
|
+
final_result = max(prediction_result.items(), key=lambda x: x[1])[0]
|
|
545
|
+
confidence = max(prediction_result.values())
|
|
546
|
+
else: # regression
|
|
547
|
+
prediction_value = ai_service.predict_numeric(processed_features)
|
|
548
|
+
final_result = prediction_value
|
|
549
|
+
confidence = 0.8 + random.random() * 0.15 # Mock confidence
|
|
550
|
+
|
|
551
|
+
# Validate confidence
|
|
552
|
+
if confidence < self.ai_config["confidence_threshold"]:
|
|
553
|
+
return self.ai_config.get("fallback_value")
|
|
554
|
+
|
|
555
|
+
# Cache result
|
|
556
|
+
self.set_cached_result(cache_key, final_result)
|
|
557
|
+
|
|
558
|
+
return final_result
|
|
559
|
+
|
|
560
|
+
except Exception as e:
|
|
561
|
+
return self.ai_config.get("fallback_value")
|
|
562
|
+
|
|
563
|
+
async def _preprocess_features(self, features_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
564
|
+
"""Preprocess features before AI computation"""
|
|
565
|
+
preprocessing_func = self.ai_config.get("preprocessing")
|
|
566
|
+
if preprocessing_func and callable(preprocessing_func):
|
|
567
|
+
return await preprocessing_func(features_data)
|
|
568
|
+
return features_data
|
|
569
|
+
|
|
570
|
+
class LLMField(CreatesonlineField, AIFieldMixin):
|
|
571
|
+
"""
|
|
572
|
+
Field that generates content using Large Language Models
|
|
573
|
+
|
|
574
|
+
Examples:
|
|
575
|
+
# Generate product description
|
|
576
|
+
description = LLMField(ai_config={
|
|
577
|
+
"model": "gpt-4",
|
|
578
|
+
"prompt_template": "Write a compelling product description for {name} with features: {features}",
|
|
579
|
+
"max_tokens": 200,
|
|
580
|
+
"temperature": 0.7
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
# Generate email subject line
|
|
584
|
+
subject = LLMField(ai_config={
|
|
585
|
+
"model": "gpt-3.5-turbo",
|
|
586
|
+
"prompt_template": "Create an engaging email subject for: {content}",
|
|
587
|
+
"max_tokens": 50
|
|
588
|
+
})
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
def __init__(self, field_type="Text", *args, ai_config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
592
|
+
"""Initialize LLM field"""
|
|
593
|
+
|
|
594
|
+
# Default LLM configuration
|
|
595
|
+
default_config = {
|
|
596
|
+
"model": "internal-llm",
|
|
597
|
+
"max_tokens": 500,
|
|
598
|
+
"temperature": 0.7,
|
|
599
|
+
"provider": "internal",
|
|
600
|
+
"regenerate_on_change": True,
|
|
601
|
+
"enable_streaming": False,
|
|
602
|
+
"timeout": 30,
|
|
603
|
+
"prompt_template": "Generate content based on: {input}",
|
|
604
|
+
"fallback_content": ""
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
ai_config = {**default_config, **(ai_config or {})}
|
|
608
|
+
|
|
609
|
+
# Initialize base classes
|
|
610
|
+
CreatesonlineField.__init__(self, field_type, *args, **kwargs)
|
|
611
|
+
AIFieldMixin.__init__(self, ai_config=ai_config)
|
|
612
|
+
|
|
613
|
+
# SQLAlchemy integration
|
|
614
|
+
if SA_AVAILABLE:
|
|
615
|
+
try:
|
|
616
|
+
self.sql_field = sa.Column(sa.Text, *args, **kwargs)
|
|
617
|
+
except:
|
|
618
|
+
self.sql_field = None
|
|
619
|
+
|
|
620
|
+
async def generate_content(self, instance, template_data: Dict[str, Any]) -> str:
|
|
621
|
+
"""Generate content using LLM"""
|
|
622
|
+
if not self.ai_enabled:
|
|
623
|
+
return self.ai_config.get("fallback_content", "")
|
|
624
|
+
|
|
625
|
+
# Generate cache key
|
|
626
|
+
cache_key = self.generate_cache_key(template_data)
|
|
627
|
+
|
|
628
|
+
# Check cache first
|
|
629
|
+
cached_result = self.get_cached_result(cache_key)
|
|
630
|
+
if cached_result is not None:
|
|
631
|
+
return cached_result
|
|
632
|
+
|
|
633
|
+
try:
|
|
634
|
+
# Get AI service
|
|
635
|
+
ai_service = self.get_ai_service()
|
|
636
|
+
|
|
637
|
+
# Build prompt from template
|
|
638
|
+
prompt = await self._build_prompt(template_data)
|
|
639
|
+
|
|
640
|
+
# Generate content using internal engine
|
|
641
|
+
generated_content = ai_service.generate_text(
|
|
642
|
+
prompt=prompt,
|
|
643
|
+
max_tokens=self.ai_config["max_tokens"]
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Post-process content
|
|
647
|
+
final_content = await self._postprocess_content(generated_content)
|
|
648
|
+
|
|
649
|
+
# Cache result
|
|
650
|
+
self.set_cached_result(cache_key, final_content)
|
|
651
|
+
|
|
652
|
+
return final_content
|
|
653
|
+
|
|
654
|
+
except Exception as e:
|
|
655
|
+
return self.ai_config.get("fallback_content", "")
|
|
656
|
+
|
|
657
|
+
async def _build_prompt(self, template_data: Dict[str, Any]) -> str:
|
|
658
|
+
"""Build prompt from template and data"""
|
|
659
|
+
prompt_template = self.ai_config.get("prompt_template", "Generate content based on: {input}")
|
|
660
|
+
|
|
661
|
+
try:
|
|
662
|
+
return prompt_template.format(**template_data)
|
|
663
|
+
except KeyError as e:
|
|
664
|
+
# Fallback to simple input
|
|
665
|
+
return f"Generate content based on: {template_data}"
|
|
666
|
+
|
|
667
|
+
async def _postprocess_content(self, content: str) -> str:
|
|
668
|
+
"""Post-process generated content"""
|
|
669
|
+
# Basic cleanup
|
|
670
|
+
content = content.strip()
|
|
671
|
+
|
|
672
|
+
# Apply custom post-processing if provided
|
|
673
|
+
postprocessing_func = self.ai_config.get("postprocessing")
|
|
674
|
+
if postprocessing_func and callable(postprocessing_func):
|
|
675
|
+
content = await postprocessing_func(content)
|
|
676
|
+
|
|
677
|
+
return content
|
|
678
|
+
|
|
679
|
+
class VectorField(CreatesonlineField, AIFieldMixin):
|
|
680
|
+
"""
|
|
681
|
+
Field that stores vector embeddings for similarity search
|
|
682
|
+
|
|
683
|
+
Examples:
|
|
684
|
+
# Store document embeddings
|
|
685
|
+
embedding = VectorField(dimensions=768, ai_config={
|
|
686
|
+
"model": "text-embedding-ada-002",
|
|
687
|
+
"source_field": "content",
|
|
688
|
+
"normalize": True
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
# Store image embeddings
|
|
692
|
+
image_embedding = VectorField(dimensions=512, ai_config={
|
|
693
|
+
"model": "clip-vit-base-patch32",
|
|
694
|
+
"source_field": "image_path",
|
|
695
|
+
"provider": "internal"
|
|
696
|
+
})
|
|
697
|
+
"""
|
|
698
|
+
|
|
699
|
+
def __init__(self, dimensions: int = 768, field_type="JSON", *args, ai_config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
700
|
+
"""Initialize vector field"""
|
|
701
|
+
self.dimensions = dimensions
|
|
702
|
+
|
|
703
|
+
# Default vector configuration
|
|
704
|
+
default_config = {
|
|
705
|
+
"model": "internal-embeddings",
|
|
706
|
+
"provider": "internal",
|
|
707
|
+
"normalize": True,
|
|
708
|
+
"batch_size": 100,
|
|
709
|
+
"similarity_metric": "cosine",
|
|
710
|
+
"enable_indexing": True,
|
|
711
|
+
"source_field": None
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
ai_config = {**default_config, **(ai_config or {})}
|
|
715
|
+
|
|
716
|
+
# Initialize base classes
|
|
717
|
+
CreatesonlineField.__init__(self, field_type, *args, **kwargs)
|
|
718
|
+
AIFieldMixin.__init__(self, ai_config=ai_config)
|
|
719
|
+
|
|
720
|
+
# SQLAlchemy integration
|
|
721
|
+
if SA_AVAILABLE:
|
|
722
|
+
try:
|
|
723
|
+
self.sql_field = sa.Column(sa.JSON, *args, **kwargs)
|
|
724
|
+
except:
|
|
725
|
+
self.sql_field = None
|
|
726
|
+
|
|
727
|
+
def process_value(self, value):
|
|
728
|
+
"""Process value before storing"""
|
|
729
|
+
if value is None:
|
|
730
|
+
return None
|
|
731
|
+
|
|
732
|
+
if isinstance(value, list):
|
|
733
|
+
# Validate dimensions
|
|
734
|
+
if len(value) != self.dimensions:
|
|
735
|
+
raise ValueError(f"Vector must have {self.dimensions} dimensions, got {len(value)}")
|
|
736
|
+
return json.dumps(value)
|
|
737
|
+
|
|
738
|
+
if hasattr(value, 'tolist'): # numpy array
|
|
739
|
+
if len(value) != self.dimensions:
|
|
740
|
+
raise ValueError(f"Vector must have {self.dimensions} dimensions, got {len(value)}")
|
|
741
|
+
return json.dumps(value.tolist())
|
|
742
|
+
|
|
743
|
+
if isinstance(value, str):
|
|
744
|
+
try:
|
|
745
|
+
parsed = json.loads(value)
|
|
746
|
+
if isinstance(parsed, list) and len(parsed) == self.dimensions:
|
|
747
|
+
return value
|
|
748
|
+
except:
|
|
749
|
+
pass
|
|
750
|
+
|
|
751
|
+
raise ValueError(f"Unsupported vector type: {type(value)}")
|
|
752
|
+
|
|
753
|
+
def parse_value(self, value):
|
|
754
|
+
"""Parse value when loading"""
|
|
755
|
+
if value is None:
|
|
756
|
+
return None
|
|
757
|
+
|
|
758
|
+
if isinstance(value, str):
|
|
759
|
+
return json.loads(value)
|
|
760
|
+
|
|
761
|
+
return value
|
|
762
|
+
|
|
763
|
+
async def generate_embedding(self, instance, source_data: Any) -> List[float]:
|
|
764
|
+
"""Generate vector embedding from source data"""
|
|
765
|
+
if not self.ai_enabled:
|
|
766
|
+
return [0.0] * self.dimensions
|
|
767
|
+
|
|
768
|
+
# Generate cache key
|
|
769
|
+
cache_key = self.generate_cache_key(source_data)
|
|
770
|
+
|
|
771
|
+
# Check cache first
|
|
772
|
+
cached_result = self.get_cached_result(cache_key)
|
|
773
|
+
if cached_result is not None:
|
|
774
|
+
return cached_result
|
|
775
|
+
|
|
776
|
+
try:
|
|
777
|
+
# Preprocess source data
|
|
778
|
+
processed_data = await self._preprocess_data(source_data)
|
|
779
|
+
|
|
780
|
+
# Get embedding service (always internal)
|
|
781
|
+
ai_service = self.get_ai_service()
|
|
782
|
+
|
|
783
|
+
# Generate embedding
|
|
784
|
+
embedding = ai_service.generate_embedding(
|
|
785
|
+
text=processed_data,
|
|
786
|
+
dimensions=self.dimensions
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
# Normalize if requested
|
|
790
|
+
if self.ai_config["normalize"]:
|
|
791
|
+
embedding = self._normalize_vector(embedding)
|
|
792
|
+
|
|
793
|
+
# Validate dimensions
|
|
794
|
+
if len(embedding) != self.dimensions:
|
|
795
|
+
# Pad or truncate to match dimensions
|
|
796
|
+
if len(embedding) < self.dimensions:
|
|
797
|
+
embedding.extend([0.0] * (self.dimensions - len(embedding)))
|
|
798
|
+
else:
|
|
799
|
+
embedding = embedding[:self.dimensions]
|
|
800
|
+
|
|
801
|
+
# Cache result
|
|
802
|
+
self.set_cached_result(cache_key, embedding)
|
|
803
|
+
|
|
804
|
+
return embedding
|
|
805
|
+
|
|
806
|
+
except Exception as e:
|
|
807
|
+
return [0.0] * self.dimensions
|
|
808
|
+
|
|
809
|
+
async def _preprocess_data(self, data: Any) -> str:
|
|
810
|
+
"""Preprocess data before embedding generation"""
|
|
811
|
+
if isinstance(data, str):
|
|
812
|
+
return data
|
|
813
|
+
elif isinstance(data, (dict, list)):
|
|
814
|
+
return json.dumps(data, default=str)
|
|
815
|
+
else:
|
|
816
|
+
return str(data)
|
|
817
|
+
|
|
818
|
+
def _normalize_vector(self, vector: List[float]) -> List[float]:
|
|
819
|
+
"""Normalize vector to unit length"""
|
|
820
|
+
try:
|
|
821
|
+
norm = math.sqrt(sum(x * x for x in vector))
|
|
822
|
+
if norm == 0:
|
|
823
|
+
return vector
|
|
824
|
+
return [x / norm for x in vector]
|
|
825
|
+
except:
|
|
826
|
+
return vector
|
|
827
|
+
|
|
828
|
+
def similarity(self, vector1: List[float], vector2: List[float]) -> float:
|
|
829
|
+
"""Calculate similarity between two vectors"""
|
|
830
|
+
ai_service = self.get_ai_service()
|
|
831
|
+
return ai_service.similarity(vector1, vector2)
|
|
832
|
+
|
|
833
|
+
class SmartTextField(CreatesonlineField, AIFieldMixin):
|
|
834
|
+
"""
|
|
835
|
+
Enhanced text field with AI analysis capabilities
|
|
836
|
+
|
|
837
|
+
Examples:
|
|
838
|
+
# Analyze sentiment and extract keywords
|
|
839
|
+
content = SmartTextField(ai_config={
|
|
840
|
+
"analyze_sentiment": True,
|
|
841
|
+
"extract_keywords": True,
|
|
842
|
+
"detect_language": True,
|
|
843
|
+
"content_moderation": True
|
|
844
|
+
})
|
|
845
|
+
"""
|
|
846
|
+
|
|
847
|
+
def __init__(self, field_type="Text", *args, ai_config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
848
|
+
"""Initialize smart text field"""
|
|
849
|
+
|
|
850
|
+
default_config = {
|
|
851
|
+
"analyze_sentiment": False,
|
|
852
|
+
"extract_keywords": False,
|
|
853
|
+
"detect_language": False,
|
|
854
|
+
"content_moderation": False,
|
|
855
|
+
"summarize": False,
|
|
856
|
+
"auto_enhance": False
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
ai_config = {**default_config, **(ai_config or {})}
|
|
860
|
+
|
|
861
|
+
# Initialize base classes
|
|
862
|
+
CreatesonlineField.__init__(self, field_type, *args, **kwargs)
|
|
863
|
+
AIFieldMixin.__init__(self, ai_config=ai_config)
|
|
864
|
+
|
|
865
|
+
# SQLAlchemy integration
|
|
866
|
+
if SA_AVAILABLE:
|
|
867
|
+
try:
|
|
868
|
+
self.sql_field = sa.Column(sa.Text, *args, **kwargs)
|
|
869
|
+
except:
|
|
870
|
+
self.sql_field = None
|
|
871
|
+
|
|
872
|
+
async def analyze_text(self, text: str) -> Dict[str, Any]:
|
|
873
|
+
"""Perform AI analysis on text content"""
|
|
874
|
+
if not self.ai_enabled or not text:
|
|
875
|
+
return {}
|
|
876
|
+
|
|
877
|
+
cache_key = self.generate_cache_key(text)
|
|
878
|
+
cached_result = self.get_cached_result(cache_key)
|
|
879
|
+
if cached_result is not None:
|
|
880
|
+
return cached_result
|
|
881
|
+
|
|
882
|
+
analysis_results = {}
|
|
883
|
+
|
|
884
|
+
try:
|
|
885
|
+
ai_service = self.get_ai_service()
|
|
886
|
+
|
|
887
|
+
# Sentiment analysis
|
|
888
|
+
if self.ai_config["analyze_sentiment"]:
|
|
889
|
+
sentiment_scores = ai_service.classify_text(text, ["positive", "negative", "neutral"])
|
|
890
|
+
analysis_results["sentiment"] = {
|
|
891
|
+
"label": max(sentiment_scores.items(), key=lambda x: x[1])[0],
|
|
892
|
+
"scores": sentiment_scores
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
# Keyword extraction (simple implementation)
|
|
896
|
+
if self.ai_config["extract_keywords"]:
|
|
897
|
+
words = re.findall(r'\b\w+\b', text.lower())
|
|
898
|
+
# Filter common words and get unique words by frequency
|
|
899
|
+
common_words = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by"}
|
|
900
|
+
keywords = [word for word in set(words) if len(word) > 3 and word not in common_words]
|
|
901
|
+
analysis_results["keywords"] = keywords[:10] # Top 10
|
|
902
|
+
|
|
903
|
+
# Language detection (simple heuristic)
|
|
904
|
+
if self.ai_config["detect_language"]:
|
|
905
|
+
if any(word in text.lower() for word in ['hola', 'gracias', 'por favor', 'español']):
|
|
906
|
+
language = 'es'
|
|
907
|
+
elif any(word in text.lower() for word in ['bonjour', 'merci', 'français']):
|
|
908
|
+
language = 'fr'
|
|
909
|
+
elif any(word in text.lower() for word in ['guten tag', 'danke', 'deutsch']):
|
|
910
|
+
language = 'de'
|
|
911
|
+
else:
|
|
912
|
+
language = 'en'
|
|
913
|
+
analysis_results["language"] = language
|
|
914
|
+
|
|
915
|
+
# Content moderation (basic)
|
|
916
|
+
if self.ai_config["content_moderation"]:
|
|
917
|
+
flagged_words = {"spam", "scam", "offensive", "inappropriate"}
|
|
918
|
+
is_flagged = any(word in text.lower() for word in flagged_words)
|
|
919
|
+
analysis_results["moderation"] = {
|
|
920
|
+
"flagged": is_flagged,
|
|
921
|
+
"confidence": 0.8 if is_flagged else 0.1
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
# Summarization
|
|
925
|
+
if self.ai_config["summarize"] and len(text) > 100:
|
|
926
|
+
summary = ai_service.generate_text(f"Summarize: {text[:200]}")
|
|
927
|
+
analysis_results["summary"] = summary
|
|
928
|
+
|
|
929
|
+
# Cache results
|
|
930
|
+
self.set_cached_result(cache_key, analysis_results)
|
|
931
|
+
|
|
932
|
+
return analysis_results
|
|
933
|
+
|
|
934
|
+
except Exception as e:
|
|
935
|
+
return {}
|
|
936
|
+
|
|
937
|
+
class PredictionField(AIComputedField):
|
|
938
|
+
"""
|
|
939
|
+
Field that provides real-time predictions based on current data
|
|
940
|
+
|
|
941
|
+
Examples:
|
|
942
|
+
# Predict next purchase date
|
|
943
|
+
next_purchase = PredictionField("Date", ai_config={
|
|
944
|
+
"model": "purchase_predictor",
|
|
945
|
+
"features": ["last_purchase", "purchase_frequency", "user_engagement"],
|
|
946
|
+
"prediction_horizon": "30_days"
|
|
947
|
+
})
|
|
948
|
+
"""
|
|
949
|
+
|
|
950
|
+
def __init__(self, field_type="Float", *args, ai_config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
951
|
+
"""Initialize prediction field"""
|
|
952
|
+
|
|
953
|
+
default_config = {
|
|
954
|
+
"model": "default_predictor",
|
|
955
|
+
"update_frequency": "daily",
|
|
956
|
+
"confidence_threshold": 0.7,
|
|
957
|
+
"prediction_horizon": "7_days",
|
|
958
|
+
"prediction_type": "regression"
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
ai_config = {**default_config, **(ai_config or {})}
|
|
962
|
+
|
|
963
|
+
super().__init__(field_type, *args, ai_config=ai_config, **kwargs)
|
|
964
|
+
|
|
965
|
+
class EmbeddingField(VectorField):
|
|
966
|
+
"""
|
|
967
|
+
Specialized vector field optimized for embedding storage and retrieval
|
|
968
|
+
|
|
969
|
+
Examples:
|
|
970
|
+
# Store product embeddings for recommendation
|
|
971
|
+
product_embedding = EmbeddingField(
|
|
972
|
+
dimensions=768,
|
|
973
|
+
ai_config={
|
|
974
|
+
"model": "sentence-transformers/all-MiniLM-L6-v2",
|
|
975
|
+
"source_field": "description",
|
|
976
|
+
"enable_similarity_search": True
|
|
977
|
+
}
|
|
978
|
+
)
|
|
979
|
+
"""
|
|
980
|
+
|
|
981
|
+
def __init__(self, dimensions: int = 768, *args, ai_config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
982
|
+
"""Initialize embedding field with optimized defaults"""
|
|
983
|
+
|
|
984
|
+
default_config = {
|
|
985
|
+
"model": "internal-embeddings",
|
|
986
|
+
"provider": "internal",
|
|
987
|
+
"normalize": True,
|
|
988
|
+
"enable_similarity_search": True,
|
|
989
|
+
"similarity_threshold": 0.8,
|
|
990
|
+
"index_type": "flat"
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
ai_config = {**default_config, **(ai_config or {})}
|
|
994
|
+
|
|
995
|
+
super().__init__(dimensions=dimensions, ai_config=ai_config, *args, **kwargs)
|
|
996
|
+
|
|
997
|
+
async def find_similar(self, query_vector: List[float], candidates: List[Dict[str, Any]], top_k: int = 10) -> List[Dict[str, Any]]:
|
|
998
|
+
"""Find similar embeddings using vector search"""
|
|
999
|
+
|
|
1000
|
+
if not candidates:
|
|
1001
|
+
return []
|
|
1002
|
+
|
|
1003
|
+
# Calculate similarities
|
|
1004
|
+
similarities = []
|
|
1005
|
+
for candidate in candidates:
|
|
1006
|
+
if "embedding" in candidate and candidate["embedding"]:
|
|
1007
|
+
similarity = self.similarity(query_vector, candidate["embedding"])
|
|
1008
|
+
if similarity >= self.ai_config["similarity_threshold"]:
|
|
1009
|
+
similarities.append({
|
|
1010
|
+
**candidate,
|
|
1011
|
+
"similarity": similarity
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
# Sort by similarity and return top_k
|
|
1015
|
+
similarities.sort(key=lambda x: x["similarity"], reverse=True)
|
|
1016
|
+
# ========================================
|
|
1017
|
+
# EXPORT AND MODULE INTERFACE
|
|
1018
|
+
# ========================================
|
|
1019
|
+
|
|
1020
|
+
# Main field types for export
|
|
1021
|
+
__all__ = [
|
|
1022
|
+
# Core AI field types
|
|
1023
|
+
"AIComputedField",
|
|
1024
|
+
"LLMField",
|
|
1025
|
+
"VectorField",
|
|
1026
|
+
"SmartTextField",
|
|
1027
|
+
"PredictionField",
|
|
1028
|
+
"EmbeddingField",
|
|
1029
|
+
|
|
1030
|
+
# Base classes and mixins
|
|
1031
|
+
"AIFieldMixin",
|
|
1032
|
+
"CreatesonlineField",
|
|
1033
|
+
"AIModelMixin",
|
|
1034
|
+
|
|
1035
|
+
# Utilities
|
|
1036
|
+
"AIFieldManager",
|
|
1037
|
+
"get_ai_field_manager",
|
|
1038
|
+
"validate_ai_config",
|
|
1039
|
+
"check_ai_field_compatibility",
|
|
1040
|
+
|
|
1041
|
+
# Internal engine (for testing)
|
|
1042
|
+
"InternalAIEngine",
|
|
1043
|
+
]
|
|
1044
|
+
|
|
1045
|
+
# ========================================
|
|
1046
|
+
# CONVENIENCE FUNCTIONS
|
|
1047
|
+
# ========================================
|
|
1048
|
+
|
|
1049
|
+
def create_smart_model(model_name: str, fields: Dict[str, Any]) -> type:
|
|
1050
|
+
"""Create a model class with AI fields dynamically"""
|
|
1051
|
+
|
|
1052
|
+
class_attrs = {
|
|
1053
|
+
"__tablename__": model_name.lower(),
|
|
1054
|
+
"__module__": __name__,
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
# Add standard fields
|
|
1058
|
+
if SA_AVAILABLE:
|
|
1059
|
+
class_attrs["id"] = sa.Column(sa.Integer, primary_key=True)
|
|
1060
|
+
class_attrs["created_at"] = sa.Column(sa.DateTime, default=datetime.utcnow)
|
|
1061
|
+
class_attrs["updated_at"] = sa.Column(sa.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
1062
|
+
|
|
1063
|
+
# Add AI fields
|
|
1064
|
+
for field_name, field_config in fields.items():
|
|
1065
|
+
field_type = field_config.get("type", "AIComputedField")
|
|
1066
|
+
ai_config = field_config.get("ai_config", {})
|
|
1067
|
+
|
|
1068
|
+
if field_type == "AIComputedField":
|
|
1069
|
+
data_type = field_config.get("data_type", "Float")
|
|
1070
|
+
class_attrs[field_name] = AIComputedField(data_type, ai_config=ai_config)
|
|
1071
|
+
elif field_type == "LLMField":
|
|
1072
|
+
class_attrs[field_name] = LLMField(ai_config=ai_config)
|
|
1073
|
+
elif field_type == "VectorField":
|
|
1074
|
+
dimensions = field_config.get("dimensions", 768)
|
|
1075
|
+
class_attrs[field_name] = VectorField(dimensions=dimensions, ai_config=ai_config)
|
|
1076
|
+
elif field_type == "SmartTextField":
|
|
1077
|
+
class_attrs[field_name] = SmartTextField(ai_config=ai_config)
|
|
1078
|
+
|
|
1079
|
+
# Create the class
|
|
1080
|
+
if SA_AVAILABLE:
|
|
1081
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
1082
|
+
Base = declarative_base()
|
|
1083
|
+
return type(model_name, (Base, AIModelMixin), class_attrs)
|
|
1084
|
+
else:
|
|
1085
|
+
return type(model_name, (AIModelMixin,), class_attrs)
|
|
1086
|
+
|
|
1087
|
+
def get_field_info() -> Dict[str, Any]:
|
|
1088
|
+
"""Get information about available AI field types"""
|
|
1089
|
+
return {
|
|
1090
|
+
"framework": "CREATESONLINE",
|
|
1091
|
+
"version": "0.1.0",
|
|
1092
|
+
"ai_fields": {
|
|
1093
|
+
"AIComputedField": {
|
|
1094
|
+
"description": "Automatically computes values using ML models",
|
|
1095
|
+
"use_cases": ["predictions", "scoring", "classification"],
|
|
1096
|
+
"data_types": ["Float", "Integer", "String", "Text"],
|
|
1097
|
+
"ai_config_options": [
|
|
1098
|
+
"model", "prediction_type", "features", "source_field",
|
|
1099
|
+
"confidence_threshold", "fallback_value"
|
|
1100
|
+
]
|
|
1101
|
+
},
|
|
1102
|
+
"LLMField": {
|
|
1103
|
+
"description": "Generates content using Large Language Models",
|
|
1104
|
+
"use_cases": ["content generation", "summarization", "translation"],
|
|
1105
|
+
"data_types": ["Text"],
|
|
1106
|
+
"ai_config_options": [
|
|
1107
|
+
"model", "prompt_template", "max_tokens", "temperature",
|
|
1108
|
+
"provider", "fallback_content"
|
|
1109
|
+
]
|
|
1110
|
+
},
|
|
1111
|
+
"VectorField": {
|
|
1112
|
+
"description": "Stores vector embeddings for similarity search",
|
|
1113
|
+
"use_cases": ["semantic search", "recommendations", "clustering"],
|
|
1114
|
+
"data_types": ["JSON"],
|
|
1115
|
+
"ai_config_options": [
|
|
1116
|
+
"model", "dimensions", "normalize", "similarity_metric",
|
|
1117
|
+
"source_field", "provider"
|
|
1118
|
+
]
|
|
1119
|
+
},
|
|
1120
|
+
"SmartTextField": {
|
|
1121
|
+
"description": "Enhanced text field with AI analysis",
|
|
1122
|
+
"use_cases": ["sentiment analysis", "keyword extraction", "language detection"],
|
|
1123
|
+
"data_types": ["Text"],
|
|
1124
|
+
"ai_config_options": [
|
|
1125
|
+
"analyze_sentiment", "extract_keywords", "detect_language",
|
|
1126
|
+
"content_moderation", "summarize"
|
|
1127
|
+
]
|
|
1128
|
+
},
|
|
1129
|
+
"PredictionField": {
|
|
1130
|
+
"description": "Real-time predictions based on current data",
|
|
1131
|
+
"use_cases": ["forecasting", "trend analysis", "risk assessment"],
|
|
1132
|
+
"data_types": ["Float", "Integer", "Date"],
|
|
1133
|
+
"ai_config_options": [
|
|
1134
|
+
"model", "prediction_horizon", "update_frequency",
|
|
1135
|
+
"features", "confidence_threshold"
|
|
1136
|
+
]
|
|
1137
|
+
},
|
|
1138
|
+
"EmbeddingField": {
|
|
1139
|
+
"description": "Optimized vector field for embeddings",
|
|
1140
|
+
"use_cases": ["document similarity", "product recommendations", "search"],
|
|
1141
|
+
"data_types": ["JSON"],
|
|
1142
|
+
"ai_config_options": [
|
|
1143
|
+
"model", "dimensions", "similarity_threshold",
|
|
1144
|
+
"enable_similarity_search", "index_type"
|
|
1145
|
+
]
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
"providers": {
|
|
1149
|
+
"internal": {
|
|
1150
|
+
"description": "Built-in CREATESONLINE AI engine",
|
|
1151
|
+
"features": ["Mock predictions", "Hash-based embeddings", "Rule-based text analysis"],
|
|
1152
|
+
"dependencies": "None - Pure Python"
|
|
1153
|
+
},
|
|
1154
|
+
"openai": {
|
|
1155
|
+
"description": "OpenAI API integration",
|
|
1156
|
+
"features": ["GPT models", "Text embeddings", "Moderation"],
|
|
1157
|
+
"dependencies": "openai package + API key"
|
|
1158
|
+
},
|
|
1159
|
+
"anthropic": {
|
|
1160
|
+
"description": "Anthropic Claude integration",
|
|
1161
|
+
"features": ["Claude models", "Text generation"],
|
|
1162
|
+
"dependencies": "anthropic package + API key"
|
|
1163
|
+
}
|
|
1164
|
+
},
|
|
1165
|
+
"configuration": {
|
|
1166
|
+
"global_settings": [
|
|
1167
|
+
"default_provider", "cache_ttl", "enable_async",
|
|
1168
|
+
"timeout", "batch_size"
|
|
1169
|
+
],
|
|
1170
|
+
"caching": {
|
|
1171
|
+
"enabled": True,
|
|
1172
|
+
"ttl": 3600,
|
|
1173
|
+
"size_limit": "100MB"
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
# ========================================
|
|
1179
|
+
# DEMONSTRATION AND EXAMPLES
|
|
1180
|
+
# ========================================
|
|
1181
|
+
|
|
1182
|
+
def create_example_model():
|
|
1183
|
+
"""Create an example model with AI fields for demonstration"""
|
|
1184
|
+
|
|
1185
|
+
# Example AI field configurations
|
|
1186
|
+
ai_fields = {
|
|
1187
|
+
"lead_score": {
|
|
1188
|
+
"type": "AIComputedField",
|
|
1189
|
+
"data_type": "Float",
|
|
1190
|
+
"ai_config": {
|
|
1191
|
+
"model": "lead_classifier",
|
|
1192
|
+
"prediction_type": "regression",
|
|
1193
|
+
"features": ["company_size", "industry", "engagement_score"],
|
|
1194
|
+
"confidence_threshold": 0.7,
|
|
1195
|
+
"fallback_value": 0.5
|
|
1196
|
+
}
|
|
1197
|
+
},
|
|
1198
|
+
"summary": {
|
|
1199
|
+
"type": "LLMField",
|
|
1200
|
+
"ai_config": {
|
|
1201
|
+
"model": "gpt-4",
|
|
1202
|
+
"prompt_template": "Summarize this lead: {company_name} - {description}",
|
|
1203
|
+
"max_tokens": 150,
|
|
1204
|
+
"temperature": 0.3
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1207
|
+
"content_embedding": {
|
|
1208
|
+
"type": "VectorField",
|
|
1209
|
+
"dimensions": 768,
|
|
1210
|
+
"ai_config": {
|
|
1211
|
+
"model": "text-embedding-ada-002",
|
|
1212
|
+
"source_field": "description",
|
|
1213
|
+
"normalize": True
|
|
1214
|
+
}
|
|
1215
|
+
},
|
|
1216
|
+
"sentiment": {
|
|
1217
|
+
"type": "SmartTextField",
|
|
1218
|
+
"ai_config": {
|
|
1219
|
+
"analyze_sentiment": True,
|
|
1220
|
+
"extract_keywords": True,
|
|
1221
|
+
"detect_language": True
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
return create_smart_model("ExampleLead", ai_fields)
|