remdb 0.3.14__py3-none-any.whl → 0.3.157__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.
- rem/agentic/README.md +76 -0
- rem/agentic/__init__.py +15 -0
- rem/agentic/agents/__init__.py +32 -2
- rem/agentic/agents/agent_manager.py +310 -0
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +51 -27
- rem/agentic/context_builder.py +5 -3
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/tool_wrapper.py +155 -18
- rem/agentic/otel/setup.py +93 -4
- rem/agentic/providers/phoenix.py +371 -108
- rem/agentic/providers/pydantic_ai.py +280 -57
- rem/agentic/schema.py +361 -21
- rem/agentic/tools/rem_tools.py +3 -3
- rem/api/README.md +215 -1
- rem/api/deps.py +255 -0
- rem/api/main.py +132 -40
- rem/api/mcp_router/resources.py +1 -1
- rem/api/mcp_router/server.py +28 -5
- rem/api/mcp_router/tools.py +555 -7
- rem/api/routers/admin.py +494 -0
- rem/api/routers/auth.py +278 -4
- rem/api/routers/chat/completions.py +402 -20
- rem/api/routers/chat/models.py +88 -10
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +542 -0
- rem/api/routers/chat/streaming.py +697 -45
- rem/api/routers/dev.py +81 -0
- rem/api/routers/feedback.py +268 -0
- rem/api/routers/messages.py +473 -0
- rem/api/routers/models.py +78 -0
- rem/api/routers/query.py +360 -0
- rem/api/routers/shared_sessions.py +406 -0
- rem/auth/__init__.py +13 -3
- rem/auth/middleware.py +186 -22
- rem/auth/providers/__init__.py +4 -1
- rem/auth/providers/email.py +215 -0
- rem/cli/commands/README.md +237 -64
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +4 -7
- rem/cli/commands/db.py +386 -143
- rem/cli/commands/experiments.py +468 -76
- rem/cli/commands/process.py +14 -8
- rem/cli/commands/schema.py +97 -50
- rem/cli/commands/session.py +336 -0
- rem/cli/dreaming.py +2 -2
- rem/cli/main.py +29 -6
- rem/config.py +10 -3
- rem/models/core/core_model.py +7 -1
- rem/models/core/experiment.py +58 -14
- rem/models/core/rem_query.py +5 -2
- rem/models/entities/__init__.py +25 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/message.py +30 -1
- rem/models/entities/ontology.py +1 -1
- rem/models/entities/ontology_config.py +1 -1
- rem/models/entities/session.py +83 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/subscriber.py +175 -0
- rem/models/entities/user.py +1 -0
- rem/registry.py +10 -4
- rem/schemas/agents/core/agent-builder.yaml +134 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +1 -1
- rem/schemas/agents/examples/contract-extractor.yaml +1 -1
- rem/schemas/agents/examples/cv-parser.yaml +1 -1
- rem/schemas/agents/rem.yaml +7 -3
- rem/services/__init__.py +3 -1
- rem/services/content/service.py +92 -19
- rem/services/email/__init__.py +10 -0
- rem/services/email/service.py +459 -0
- rem/services/email/templates.py +360 -0
- rem/services/embeddings/api.py +4 -4
- rem/services/embeddings/worker.py +16 -16
- rem/services/phoenix/client.py +154 -14
- rem/services/postgres/README.md +197 -15
- rem/services/postgres/__init__.py +2 -1
- rem/services/postgres/diff_service.py +547 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +470 -140
- rem/services/postgres/repository.py +132 -0
- rem/services/postgres/schema_generator.py +205 -4
- rem/services/postgres/service.py +6 -6
- rem/services/rem/parser.py +44 -9
- rem/services/rem/service.py +36 -2
- rem/services/session/compression.py +137 -51
- rem/services/session/reload.py +15 -8
- rem/settings.py +515 -27
- rem/sql/background_indexes.sql +21 -16
- rem/sql/migrations/001_install.sql +387 -54
- rem/sql/migrations/002_install_models.sql +2304 -377
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +548 -0
- rem/sql/migrations/005_schema_update.sql +145 -0
- rem/utils/README.md +45 -0
- rem/utils/__init__.py +18 -0
- rem/utils/date_utils.py +2 -2
- rem/utils/files.py +157 -1
- rem/utils/model_helpers.py +156 -1
- rem/utils/schema_loader.py +220 -22
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +3 -1
- rem/utils/vision.py +1 -1
- rem/workers/__init__.py +3 -1
- rem/workers/db_listener.py +579 -0
- rem/workers/unlogged_maintainer.py +463 -0
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/METADATA +340 -229
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/RECORD +109 -80
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/WHEEL +1 -1
- rem/sql/002_install_models.sql +0 -1068
- rem/sql/install_models.sql +0 -1051
- rem/sql/migrations/003_seed_default_user.sql +0 -48
- {remdb-0.3.14.dist-info → remdb-0.3.157.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Email Templates.
|
|
3
|
+
|
|
4
|
+
HTML email templates for transactional emails.
|
|
5
|
+
Uses inline CSS for maximum email client compatibility.
|
|
6
|
+
|
|
7
|
+
Downstream apps can customize by:
|
|
8
|
+
1. Overriding COLORS dict
|
|
9
|
+
2. Setting custom LOGO_URL and TAGLINE
|
|
10
|
+
3. Creating custom templates using base_template()
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Default colors - override in downstream apps
|
|
18
|
+
COLORS = {
|
|
19
|
+
"background": "#F5F5F5",
|
|
20
|
+
"foreground": "#333333",
|
|
21
|
+
"primary": "#4A90D9",
|
|
22
|
+
"accent": "#5CB85C",
|
|
23
|
+
"card": "#FFFFFF",
|
|
24
|
+
"muted": "#6b7280",
|
|
25
|
+
"border": "#E0E0E0",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Branding - override in downstream apps
|
|
29
|
+
LOGO_URL: str | None = None
|
|
30
|
+
APP_NAME = "REM"
|
|
31
|
+
TAGLINE = "Your AI-powered platform"
|
|
32
|
+
WEBSITE_URL = "https://rem.ai"
|
|
33
|
+
PRIVACY_URL = "https://rem.ai/privacy"
|
|
34
|
+
TERMS_URL = "https://rem.ai/terms"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class EmailTemplate:
|
|
39
|
+
"""Email template with subject and HTML body."""
|
|
40
|
+
|
|
41
|
+
subject: str
|
|
42
|
+
html_body: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def base_template(
|
|
46
|
+
content: str,
|
|
47
|
+
preheader: Optional[str] = None,
|
|
48
|
+
colors: dict | None = None,
|
|
49
|
+
logo_url: str | None = None,
|
|
50
|
+
app_name: str | None = None,
|
|
51
|
+
tagline: str | None = None,
|
|
52
|
+
website_url: str | None = None,
|
|
53
|
+
privacy_url: str | None = None,
|
|
54
|
+
terms_url: str | None = None,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""
|
|
57
|
+
Base email template.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
content: The main content HTML
|
|
61
|
+
preheader: Optional preview text shown in email clients
|
|
62
|
+
colors: Color overrides (merges with COLORS)
|
|
63
|
+
logo_url: Logo image URL
|
|
64
|
+
app_name: Application name
|
|
65
|
+
tagline: Footer tagline
|
|
66
|
+
website_url: Main website URL
|
|
67
|
+
privacy_url: Privacy policy URL
|
|
68
|
+
terms_url: Terms of service URL
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Complete HTML email
|
|
72
|
+
"""
|
|
73
|
+
# Merge colors
|
|
74
|
+
c = {**COLORS, **(colors or {})}
|
|
75
|
+
|
|
76
|
+
# Use provided values or module defaults
|
|
77
|
+
logo = logo_url or LOGO_URL
|
|
78
|
+
name = app_name or APP_NAME
|
|
79
|
+
tag = tagline or TAGLINE
|
|
80
|
+
web = website_url or WEBSITE_URL
|
|
81
|
+
privacy = privacy_url or PRIVACY_URL
|
|
82
|
+
terms = terms_url or TERMS_URL
|
|
83
|
+
|
|
84
|
+
preheader_html = ""
|
|
85
|
+
if preheader:
|
|
86
|
+
preheader_html = f'''
|
|
87
|
+
<div style="display: none; max-height: 0; overflow: hidden;">
|
|
88
|
+
{preheader}
|
|
89
|
+
</div>
|
|
90
|
+
'''
|
|
91
|
+
|
|
92
|
+
logo_html = ""
|
|
93
|
+
if logo:
|
|
94
|
+
logo_html = f'''
|
|
95
|
+
<img src="{logo}" alt="{name}" width="40" height="40" style="display: block; margin: 0 auto 16px auto; border-radius: 8px;">
|
|
96
|
+
'''
|
|
97
|
+
|
|
98
|
+
return f'''<!DOCTYPE html>
|
|
99
|
+
<html lang="en">
|
|
100
|
+
<head>
|
|
101
|
+
<meta charset="UTF-8">
|
|
102
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
103
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
104
|
+
<title>{name}</title>
|
|
105
|
+
<!--[if mso]>
|
|
106
|
+
<style type="text/css">
|
|
107
|
+
body, table, td {{font-family: Arial, sans-serif !important;}}
|
|
108
|
+
</style>
|
|
109
|
+
<![endif]-->
|
|
110
|
+
</head>
|
|
111
|
+
<body style="margin: 0; padding: 0; background-color: {c['background']}; font-family: 'Georgia', 'Times New Roman', serif;">
|
|
112
|
+
{preheader_html}
|
|
113
|
+
|
|
114
|
+
<!-- Email Container -->
|
|
115
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="background-color: {c['background']};">
|
|
116
|
+
<tr>
|
|
117
|
+
<td style="padding: 40px 20px;">
|
|
118
|
+
<!-- Inner Container -->
|
|
119
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="max-width: 560px; margin: 0 auto;">
|
|
120
|
+
|
|
121
|
+
<!-- Main Content Card -->
|
|
122
|
+
<tr>
|
|
123
|
+
<td style="background-color: {c['card']}; border-radius: 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);">
|
|
124
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
|
125
|
+
<tr>
|
|
126
|
+
<td style="padding: 40px;">
|
|
127
|
+
{content}
|
|
128
|
+
</td>
|
|
129
|
+
</tr>
|
|
130
|
+
</table>
|
|
131
|
+
</td>
|
|
132
|
+
</tr>
|
|
133
|
+
|
|
134
|
+
<!-- Footer -->
|
|
135
|
+
<tr>
|
|
136
|
+
<td style="padding: 32px 20px; text-align: center;">
|
|
137
|
+
{logo_html}
|
|
138
|
+
|
|
139
|
+
<!-- Tagline -->
|
|
140
|
+
<p style="margin: 0 0 8px 0; font-family: 'Georgia', serif; font-size: 14px; color: {c['muted']}; font-style: italic;">
|
|
141
|
+
{tag}
|
|
142
|
+
</p>
|
|
143
|
+
|
|
144
|
+
<!-- Footer Links -->
|
|
145
|
+
<p style="margin: 0; font-family: Arial, sans-serif; font-size: 12px; color: {c['muted']};">
|
|
146
|
+
<a href="{web}" style="color: {c['primary']}; text-decoration: none;">{name}</a>
|
|
147
|
+
•
|
|
148
|
+
<a href="{privacy}" style="color: {c['primary']}; text-decoration: none;">Privacy</a>
|
|
149
|
+
•
|
|
150
|
+
<a href="{terms}" style="color: {c['primary']}; text-decoration: none;">Terms</a>
|
|
151
|
+
</p>
|
|
152
|
+
|
|
153
|
+
<!-- Copyright -->
|
|
154
|
+
<p style="margin: 16px 0 0 0; font-family: Arial, sans-serif; font-size: 11px; color: {c['muted']};">
|
|
155
|
+
© 2025 {name}. All rights reserved.
|
|
156
|
+
</p>
|
|
157
|
+
</td>
|
|
158
|
+
</tr>
|
|
159
|
+
|
|
160
|
+
</table>
|
|
161
|
+
</td>
|
|
162
|
+
</tr>
|
|
163
|
+
</table>
|
|
164
|
+
</body>
|
|
165
|
+
</html>'''
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def login_code_template(
|
|
169
|
+
code: str,
|
|
170
|
+
email: str,
|
|
171
|
+
colors: dict | None = None,
|
|
172
|
+
app_name: str | None = None,
|
|
173
|
+
**kwargs,
|
|
174
|
+
) -> EmailTemplate:
|
|
175
|
+
"""
|
|
176
|
+
Generate a login code email template.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
code: The 6-digit login code
|
|
180
|
+
email: The recipient's email address
|
|
181
|
+
colors: Color overrides
|
|
182
|
+
app_name: Application name
|
|
183
|
+
**kwargs: Additional arguments passed to base_template
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
EmailTemplate with subject and HTML body
|
|
187
|
+
"""
|
|
188
|
+
c = {**COLORS, **(colors or {})}
|
|
189
|
+
name = app_name or APP_NAME
|
|
190
|
+
|
|
191
|
+
# Format code with spaces for readability (e.g., "123 456")
|
|
192
|
+
formatted_code = f"{code[:3]} {code[3:]}" if len(code) == 6 else code
|
|
193
|
+
|
|
194
|
+
content = f'''
|
|
195
|
+
<!-- Greeting -->
|
|
196
|
+
<h1 style="margin: 0 0 8px 0; font-family: 'Arial', sans-serif; font-size: 24px; font-weight: 600; color: {c['foreground']};">
|
|
197
|
+
Hi there!
|
|
198
|
+
</h1>
|
|
199
|
+
|
|
200
|
+
<p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 16px; line-height: 1.6; color: {c['foreground']};">
|
|
201
|
+
Here's your login code for {name}. Enter this code to securely access your account.
|
|
202
|
+
</p>
|
|
203
|
+
|
|
204
|
+
<!-- Code Box -->
|
|
205
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
|
|
206
|
+
<tr>
|
|
207
|
+
<td align="center">
|
|
208
|
+
<div style="
|
|
209
|
+
display: inline-block;
|
|
210
|
+
padding: 20px 40px;
|
|
211
|
+
background-color: {c['background']};
|
|
212
|
+
border: 2px solid {c['border']};
|
|
213
|
+
border-radius: 12px;
|
|
214
|
+
">
|
|
215
|
+
<span style="
|
|
216
|
+
font-family: 'Courier New', monospace;
|
|
217
|
+
font-size: 32px;
|
|
218
|
+
font-weight: 700;
|
|
219
|
+
letter-spacing: 6px;
|
|
220
|
+
color: {c['foreground']};
|
|
221
|
+
">{formatted_code}</span>
|
|
222
|
+
</div>
|
|
223
|
+
</td>
|
|
224
|
+
</tr>
|
|
225
|
+
</table>
|
|
226
|
+
|
|
227
|
+
<!-- Instructions -->
|
|
228
|
+
<p style="margin: 0 0 8px 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']};">
|
|
229
|
+
This code expires in <strong>10 minutes</strong>.
|
|
230
|
+
</p>
|
|
231
|
+
|
|
232
|
+
<p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']};">
|
|
233
|
+
If you didn't request this code, you can safely ignore this email.
|
|
234
|
+
</p>
|
|
235
|
+
|
|
236
|
+
<!-- Divider -->
|
|
237
|
+
<hr style="border: none; border-top: 1px solid {c['border']}; margin: 24px 0;">
|
|
238
|
+
|
|
239
|
+
<!-- Security Note -->
|
|
240
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
|
241
|
+
<tr>
|
|
242
|
+
<td style="padding: 16px; background-color: {c['background']}; border-radius: 8px;">
|
|
243
|
+
<p style="margin: 0; font-family: Arial, sans-serif; font-size: 12px; color: {c['muted']};">
|
|
244
|
+
<strong style="color: {c['primary']};">Security tip:</strong>
|
|
245
|
+
Never share your login code with anyone. {name} will never ask for your code via phone or text.
|
|
246
|
+
</p>
|
|
247
|
+
</td>
|
|
248
|
+
</tr>
|
|
249
|
+
</table>
|
|
250
|
+
'''
|
|
251
|
+
|
|
252
|
+
return EmailTemplate(
|
|
253
|
+
subject=f"Your {name} Login Code",
|
|
254
|
+
html_body=base_template(
|
|
255
|
+
content=content,
|
|
256
|
+
preheader=f"Your login code is {formatted_code}",
|
|
257
|
+
colors=colors,
|
|
258
|
+
app_name=app_name,
|
|
259
|
+
**kwargs,
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def welcome_template(
|
|
265
|
+
name: Optional[str] = None,
|
|
266
|
+
colors: dict | None = None,
|
|
267
|
+
app_name: str | None = None,
|
|
268
|
+
features: list[str] | None = None,
|
|
269
|
+
cta_url: str | None = None,
|
|
270
|
+
cta_text: str = "Get Started",
|
|
271
|
+
**kwargs,
|
|
272
|
+
) -> EmailTemplate:
|
|
273
|
+
"""
|
|
274
|
+
Generate a welcome email template for new users.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
name: Optional user's name
|
|
278
|
+
colors: Color overrides
|
|
279
|
+
app_name: Application name
|
|
280
|
+
features: List of feature descriptions
|
|
281
|
+
cta_url: Call-to-action button URL
|
|
282
|
+
cta_text: Call-to-action button text
|
|
283
|
+
**kwargs: Additional arguments passed to base_template
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
EmailTemplate with subject and HTML body
|
|
287
|
+
"""
|
|
288
|
+
c = {**COLORS, **(colors or {})}
|
|
289
|
+
app = app_name or APP_NAME
|
|
290
|
+
greeting = f"Hi {name}!" if name else "Welcome!"
|
|
291
|
+
url = cta_url or WEBSITE_URL
|
|
292
|
+
|
|
293
|
+
# Default features if not provided
|
|
294
|
+
default_features = [
|
|
295
|
+
"Access powerful AI capabilities",
|
|
296
|
+
"Store and organize your data",
|
|
297
|
+
"Collaborate with your team",
|
|
298
|
+
]
|
|
299
|
+
feature_list = features or default_features
|
|
300
|
+
|
|
301
|
+
features_html = "".join(f"<li>{f}</li>" for f in feature_list)
|
|
302
|
+
|
|
303
|
+
content = f'''
|
|
304
|
+
<!-- Greeting -->
|
|
305
|
+
<h1 style="margin: 0 0 8px 0; font-family: 'Arial', sans-serif; font-size: 24px; font-weight: 600; color: {c['foreground']};">
|
|
306
|
+
{greeting}
|
|
307
|
+
</h1>
|
|
308
|
+
|
|
309
|
+
<p style="margin: 0 0 24px 0; font-family: 'Georgia', serif; font-size: 16px; line-height: 1.6; color: {c['foreground']};">
|
|
310
|
+
Welcome to {app}! We're excited to have you on board.
|
|
311
|
+
</p>
|
|
312
|
+
|
|
313
|
+
<!-- Feature List -->
|
|
314
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
|
|
315
|
+
<tr>
|
|
316
|
+
<td style="padding: 16px; background-color: {c['background']}; border-radius: 8px;">
|
|
317
|
+
<p style="margin: 0 0 12px 0; font-family: Arial, sans-serif; font-size: 14px; font-weight: 600; color: {c['primary']};">
|
|
318
|
+
What you can do with {app}:
|
|
319
|
+
</p>
|
|
320
|
+
<ul style="margin: 0; padding-left: 20px; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.8; color: {c['foreground']};">
|
|
321
|
+
{features_html}
|
|
322
|
+
</ul>
|
|
323
|
+
</td>
|
|
324
|
+
</tr>
|
|
325
|
+
</table>
|
|
326
|
+
|
|
327
|
+
<!-- CTA Button -->
|
|
328
|
+
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%" style="margin: 0 0 24px 0;">
|
|
329
|
+
<tr>
|
|
330
|
+
<td align="center">
|
|
331
|
+
<a href="{url}" style="
|
|
332
|
+
display: inline-block;
|
|
333
|
+
padding: 14px 32px;
|
|
334
|
+
background-color: {c['primary']};
|
|
335
|
+
color: #FFFFFF;
|
|
336
|
+
font-family: Arial, sans-serif;
|
|
337
|
+
font-size: 16px;
|
|
338
|
+
font-weight: 600;
|
|
339
|
+
text-decoration: none;
|
|
340
|
+
border-radius: 8px;
|
|
341
|
+
">{cta_text}</a>
|
|
342
|
+
</td>
|
|
343
|
+
</tr>
|
|
344
|
+
</table>
|
|
345
|
+
|
|
346
|
+
<p style="margin: 0; font-family: 'Georgia', serif; font-size: 14px; line-height: 1.6; color: {c['muted']}; text-align: center;">
|
|
347
|
+
We're glad you're here!
|
|
348
|
+
</p>
|
|
349
|
+
'''
|
|
350
|
+
|
|
351
|
+
return EmailTemplate(
|
|
352
|
+
subject=f"Welcome to {app}",
|
|
353
|
+
html_body=base_template(
|
|
354
|
+
content=content,
|
|
355
|
+
preheader=f"Welcome to {app}! Get started today.",
|
|
356
|
+
colors=colors,
|
|
357
|
+
app_name=app_name,
|
|
358
|
+
**kwargs,
|
|
359
|
+
),
|
|
360
|
+
)
|
rem/services/embeddings/api.py
CHANGED
|
@@ -45,7 +45,7 @@ def generate_embedding(
|
|
|
45
45
|
return [0.0] * DEFAULT_EMBEDDING_DIMS
|
|
46
46
|
|
|
47
47
|
try:
|
|
48
|
-
logger.
|
|
48
|
+
logger.debug(f"Generating OpenAI embedding for text using {model}")
|
|
49
49
|
|
|
50
50
|
response = requests.post(
|
|
51
51
|
"https://api.openai.com/v1/embeddings",
|
|
@@ -60,7 +60,7 @@ def generate_embedding(
|
|
|
60
60
|
|
|
61
61
|
data = response.json()
|
|
62
62
|
embedding = data["data"][0]["embedding"]
|
|
63
|
-
logger.
|
|
63
|
+
logger.debug(f"Successfully generated embedding (dimension: {len(embedding)})")
|
|
64
64
|
return cast(list[float], embedding)
|
|
65
65
|
|
|
66
66
|
except Exception as e:
|
|
@@ -97,7 +97,7 @@ async def generate_embedding_async(
|
|
|
97
97
|
return [0.0] * DEFAULT_EMBEDDING_DIMS
|
|
98
98
|
|
|
99
99
|
try:
|
|
100
|
-
logger.
|
|
100
|
+
logger.debug(f"Generating OpenAI embedding for text using {model}")
|
|
101
101
|
|
|
102
102
|
async with httpx.AsyncClient() as client:
|
|
103
103
|
response = await client.post(
|
|
@@ -113,7 +113,7 @@ async def generate_embedding_async(
|
|
|
113
113
|
|
|
114
114
|
data = response.json()
|
|
115
115
|
embedding = data["data"][0]["embedding"]
|
|
116
|
-
logger.
|
|
116
|
+
logger.debug(
|
|
117
117
|
f"Successfully generated embedding (dimension: {len(embedding)})"
|
|
118
118
|
)
|
|
119
119
|
return cast(list[float], embedding)
|
|
@@ -69,7 +69,7 @@ def get_global_embedding_worker(postgres_service: Any = None) -> "EmbeddingWorke
|
|
|
69
69
|
if postgres_service is None:
|
|
70
70
|
raise RuntimeError("Must provide postgres_service on first call to get_global_embedding_worker")
|
|
71
71
|
_global_worker = EmbeddingWorker(postgres_service=postgres_service)
|
|
72
|
-
logger.
|
|
72
|
+
logger.debug("Created global EmbeddingWorker singleton")
|
|
73
73
|
|
|
74
74
|
return _global_worker
|
|
75
75
|
|
|
@@ -117,7 +117,7 @@ class EmbeddingWorker:
|
|
|
117
117
|
"No OpenAI API key provided - embeddings will use zero vectors"
|
|
118
118
|
)
|
|
119
119
|
|
|
120
|
-
logger.
|
|
120
|
+
logger.debug(
|
|
121
121
|
f"Initialized EmbeddingWorker: {num_workers} workers, "
|
|
122
122
|
f"batch_size={batch_size}, timeout={batch_timeout}s"
|
|
123
123
|
)
|
|
@@ -125,17 +125,17 @@ class EmbeddingWorker:
|
|
|
125
125
|
async def start(self) -> None:
|
|
126
126
|
"""Start worker pool."""
|
|
127
127
|
if self.running:
|
|
128
|
-
logger.
|
|
128
|
+
logger.debug("EmbeddingWorker already running")
|
|
129
129
|
return
|
|
130
130
|
|
|
131
131
|
self.running = True
|
|
132
|
-
logger.
|
|
132
|
+
logger.debug(f"Starting {self.num_workers} embedding workers")
|
|
133
133
|
|
|
134
134
|
for i in range(self.num_workers):
|
|
135
135
|
worker = asyncio.create_task(self._worker_loop(i))
|
|
136
136
|
self.workers.append(worker)
|
|
137
137
|
|
|
138
|
-
logger.
|
|
138
|
+
logger.debug("EmbeddingWorker started")
|
|
139
139
|
|
|
140
140
|
async def stop(self) -> None:
|
|
141
141
|
"""Stop worker pool gracefully - processes remaining queue before stopping."""
|
|
@@ -143,7 +143,7 @@ class EmbeddingWorker:
|
|
|
143
143
|
return
|
|
144
144
|
|
|
145
145
|
queue_size = self.task_queue.qsize()
|
|
146
|
-
logger.
|
|
146
|
+
logger.debug(f"Stopping EmbeddingWorker (processing {queue_size} queued tasks first)")
|
|
147
147
|
|
|
148
148
|
# Wait for queue to drain (with timeout)
|
|
149
149
|
max_wait = 30 # 30 seconds max
|
|
@@ -171,7 +171,7 @@ class EmbeddingWorker:
|
|
|
171
171
|
await asyncio.gather(*self.workers, return_exceptions=True)
|
|
172
172
|
|
|
173
173
|
self.workers.clear()
|
|
174
|
-
logger.
|
|
174
|
+
logger.debug("EmbeddingWorker stopped")
|
|
175
175
|
|
|
176
176
|
async def queue_task(self, task: EmbeddingTask) -> None:
|
|
177
177
|
"""
|
|
@@ -195,7 +195,7 @@ class EmbeddingWorker:
|
|
|
195
195
|
Args:
|
|
196
196
|
worker_id: Unique worker identifier
|
|
197
197
|
"""
|
|
198
|
-
logger.
|
|
198
|
+
logger.debug(f"Worker {worker_id} started")
|
|
199
199
|
|
|
200
200
|
while self.running:
|
|
201
201
|
try:
|
|
@@ -205,7 +205,7 @@ class EmbeddingWorker:
|
|
|
205
205
|
if not batch:
|
|
206
206
|
continue
|
|
207
207
|
|
|
208
|
-
logger.
|
|
208
|
+
logger.debug(f"Worker {worker_id} processing batch of {len(batch)} tasks")
|
|
209
209
|
|
|
210
210
|
# Generate embeddings for batch
|
|
211
211
|
await self._process_batch(batch)
|
|
@@ -213,14 +213,14 @@ class EmbeddingWorker:
|
|
|
213
213
|
logger.debug(f"Worker {worker_id} completed batch")
|
|
214
214
|
|
|
215
215
|
except asyncio.CancelledError:
|
|
216
|
-
logger.
|
|
216
|
+
logger.debug(f"Worker {worker_id} cancelled")
|
|
217
217
|
break
|
|
218
218
|
except Exception as e:
|
|
219
219
|
logger.error(f"Worker {worker_id} error: {e}", exc_info=True)
|
|
220
220
|
# Continue processing (don't crash worker on error)
|
|
221
221
|
await asyncio.sleep(1)
|
|
222
222
|
|
|
223
|
-
logger.
|
|
223
|
+
logger.debug(f"Worker {worker_id} stopped")
|
|
224
224
|
|
|
225
225
|
async def _collect_batch(self) -> list[EmbeddingTask]:
|
|
226
226
|
"""
|
|
@@ -284,10 +284,10 @@ class EmbeddingWorker:
|
|
|
284
284
|
)
|
|
285
285
|
|
|
286
286
|
# Upsert to database
|
|
287
|
-
logger.
|
|
287
|
+
logger.debug(f"Upserting {len(embeddings)} embeddings to database...")
|
|
288
288
|
await self._upsert_embeddings(batch, embeddings)
|
|
289
289
|
|
|
290
|
-
logger.
|
|
290
|
+
logger.debug(
|
|
291
291
|
f"Successfully generated and stored {len(embeddings)} embeddings "
|
|
292
292
|
f"(provider={provider}, model={model})"
|
|
293
293
|
)
|
|
@@ -315,7 +315,7 @@ class EmbeddingWorker:
|
|
|
315
315
|
"""
|
|
316
316
|
if provider == "openai" and self.openai_api_key:
|
|
317
317
|
try:
|
|
318
|
-
logger.
|
|
318
|
+
logger.debug(
|
|
319
319
|
f"Generating OpenAI embeddings for {len(texts)} texts using {model}"
|
|
320
320
|
)
|
|
321
321
|
|
|
@@ -336,7 +336,7 @@ class EmbeddingWorker:
|
|
|
336
336
|
data = response.json()
|
|
337
337
|
embeddings = [item["embedding"] for item in data["data"]]
|
|
338
338
|
|
|
339
|
-
logger.
|
|
339
|
+
logger.debug(
|
|
340
340
|
f"Successfully generated {len(embeddings)} embeddings from OpenAI"
|
|
341
341
|
)
|
|
342
342
|
return embeddings
|
|
@@ -409,7 +409,7 @@ class EmbeddingWorker:
|
|
|
409
409
|
),
|
|
410
410
|
)
|
|
411
411
|
|
|
412
|
-
logger.
|
|
412
|
+
logger.debug(
|
|
413
413
|
f"Upserted embedding: {task.table_name}.{task.entity_id}.{task.field_name}"
|
|
414
414
|
)
|
|
415
415
|
|