remdb 0.3.180__py3-none-any.whl → 0.3.258__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 +36 -2
- rem/agentic/__init__.py +10 -1
- rem/agentic/context.py +185 -1
- rem/agentic/context_builder.py +56 -35
- rem/agentic/mcp/tool_wrapper.py +2 -2
- rem/agentic/providers/pydantic_ai.py +303 -111
- rem/agentic/schema.py +2 -2
- rem/api/main.py +1 -1
- rem/api/mcp_router/resources.py +223 -0
- rem/api/mcp_router/server.py +4 -0
- rem/api/mcp_router/tools.py +608 -166
- rem/api/routers/admin.py +30 -4
- rem/api/routers/auth.py +219 -20
- rem/api/routers/chat/child_streaming.py +393 -0
- rem/api/routers/chat/completions.py +77 -40
- rem/api/routers/chat/sse_events.py +7 -3
- rem/api/routers/chat/streaming.py +381 -291
- rem/api/routers/chat/streaming_utils.py +325 -0
- rem/api/routers/common.py +18 -0
- rem/api/routers/dev.py +7 -1
- rem/api/routers/feedback.py +11 -3
- rem/api/routers/messages.py +176 -38
- rem/api/routers/models.py +9 -1
- rem/api/routers/query.py +17 -15
- rem/api/routers/shared_sessions.py +16 -0
- rem/auth/jwt.py +19 -4
- rem/auth/middleware.py +42 -28
- rem/cli/README.md +62 -0
- rem/cli/commands/ask.py +205 -114
- rem/cli/commands/db.py +55 -31
- rem/cli/commands/experiments.py +1 -1
- rem/cli/commands/process.py +179 -43
- rem/cli/commands/query.py +109 -0
- rem/cli/commands/session.py +117 -0
- rem/cli/main.py +2 -0
- rem/models/core/experiment.py +1 -1
- rem/models/entities/ontology.py +18 -20
- rem/models/entities/session.py +1 -0
- rem/schemas/agents/core/agent-builder.yaml +1 -1
- rem/schemas/agents/rem.yaml +1 -1
- rem/schemas/agents/test_orchestrator.yaml +42 -0
- rem/schemas/agents/test_structured_output.yaml +52 -0
- rem/services/content/providers.py +151 -49
- rem/services/content/service.py +18 -5
- rem/services/embeddings/worker.py +26 -12
- rem/services/postgres/__init__.py +28 -3
- rem/services/postgres/diff_service.py +57 -5
- rem/services/postgres/programmable_diff_service.py +635 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +2 -2
- rem/services/postgres/register_type.py +11 -10
- rem/services/postgres/repository.py +39 -28
- rem/services/postgres/schema_generator.py +5 -5
- rem/services/postgres/sql_builder.py +6 -5
- rem/services/rem/README.md +4 -3
- rem/services/rem/parser.py +7 -10
- rem/services/rem/service.py +47 -0
- rem/services/session/__init__.py +8 -1
- rem/services/session/compression.py +47 -5
- rem/services/session/pydantic_messages.py +310 -0
- rem/services/session/reload.py +2 -1
- rem/settings.py +92 -7
- rem/sql/migrations/001_install.sql +125 -7
- rem/sql/migrations/002_install_models.sql +159 -149
- rem/sql/migrations/004_cache_system.sql +10 -276
- rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
- rem/utils/schema_loader.py +180 -120
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/METADATA +7 -6
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/RECORD +70 -61
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/WHEEL +0 -0
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/entry_points.txt +0 -0
rem/utils/schema_loader.py
CHANGED
|
@@ -84,6 +84,7 @@ Schema Caching Status:
|
|
|
84
84
|
"""
|
|
85
85
|
|
|
86
86
|
import importlib.resources
|
|
87
|
+
import time
|
|
87
88
|
from pathlib import Path
|
|
88
89
|
from typing import Any, cast
|
|
89
90
|
|
|
@@ -104,10 +105,32 @@ SCHEMA_SEARCH_PATHS = [
|
|
|
104
105
|
# In-memory cache for filesystem schemas (no TTL - immutable)
|
|
105
106
|
_fs_schema_cache: dict[str, dict[str, Any]] = {}
|
|
106
107
|
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
# Database schema cache (with TTL - mutable, supports hot-reload)
|
|
109
|
+
# Cache key: (schema_name, user_id or "public") → (schema_dict, timestamp)
|
|
110
|
+
_db_schema_cache: dict[tuple[str, str], tuple[dict[str, Any], float]] = {}
|
|
111
|
+
_db_schema_ttl: int = 300 # 5 minutes in seconds
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _get_cached_db_schema(schema_name: str, user_id: str | None) -> dict[str, Any] | None:
|
|
115
|
+
"""Get schema from DB cache if exists and not expired."""
|
|
116
|
+
cache_key = (schema_name.lower(), user_id or "public")
|
|
117
|
+
if cache_key in _db_schema_cache:
|
|
118
|
+
schema, timestamp = _db_schema_cache[cache_key]
|
|
119
|
+
if time.time() - timestamp < _db_schema_ttl:
|
|
120
|
+
logger.debug(f"Schema cache hit: {schema_name} (age: {time.time() - timestamp:.0f}s)")
|
|
121
|
+
return schema
|
|
122
|
+
else:
|
|
123
|
+
# Expired, remove from cache
|
|
124
|
+
del _db_schema_cache[cache_key]
|
|
125
|
+
logger.debug(f"Schema cache expired: {schema_name}")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _cache_db_schema(schema_name: str, user_id: str | None, schema: dict[str, Any]) -> None:
|
|
130
|
+
"""Add schema to DB cache with current timestamp."""
|
|
131
|
+
cache_key = (schema_name.lower(), user_id or "public")
|
|
132
|
+
_db_schema_cache[cache_key] = (schema, time.time())
|
|
133
|
+
logger.debug(f"Schema cached: {schema_name} (TTL: {_db_schema_ttl}s)")
|
|
111
134
|
|
|
112
135
|
|
|
113
136
|
def _load_schema_from_database(schema_name: str, user_id: str) -> dict[str, Any] | None:
|
|
@@ -147,15 +170,25 @@ def _load_schema_from_database(schema_name: str, user_id: str) -> dict[str, Any]
|
|
|
147
170
|
try:
|
|
148
171
|
await db.connect()
|
|
149
172
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
173
|
+
# Query for public schemas (user_id IS NULL) and optionally user-specific
|
|
174
|
+
if user_id:
|
|
175
|
+
query = """
|
|
176
|
+
SELECT spec FROM schemas
|
|
177
|
+
WHERE LOWER(name) = LOWER($1)
|
|
178
|
+
AND (user_id = $2 OR user_id = 'system' OR user_id IS NULL)
|
|
179
|
+
LIMIT 1
|
|
180
|
+
"""
|
|
181
|
+
row = await db.fetchrow(query, schema_name, user_id)
|
|
182
|
+
else:
|
|
183
|
+
# No user_id - only search public schemas
|
|
184
|
+
query = """
|
|
185
|
+
SELECT spec FROM schemas
|
|
186
|
+
WHERE LOWER(name) = LOWER($1)
|
|
187
|
+
AND (user_id = 'system' OR user_id IS NULL)
|
|
188
|
+
LIMIT 1
|
|
189
|
+
"""
|
|
190
|
+
row = await db.fetchrow(query, schema_name)
|
|
191
|
+
logger.debug(f"Executing schema lookup: name={schema_name}, user_id={user_id or 'public'}")
|
|
159
192
|
|
|
160
193
|
if row:
|
|
161
194
|
spec = row.get("spec")
|
|
@@ -193,17 +226,25 @@ def _load_schema_from_database(schema_name: str, user_id: str) -> dict[str, Any]
|
|
|
193
226
|
try:
|
|
194
227
|
await db.connect()
|
|
195
228
|
|
|
196
|
-
# Query schemas
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
229
|
+
# Query for public schemas (user_id IS NULL) and optionally user-specific
|
|
230
|
+
if user_id:
|
|
231
|
+
query = """
|
|
232
|
+
SELECT spec FROM schemas
|
|
233
|
+
WHERE LOWER(name) = LOWER($1)
|
|
234
|
+
AND (user_id = $2 OR user_id = 'system' OR user_id IS NULL)
|
|
235
|
+
LIMIT 1
|
|
236
|
+
"""
|
|
237
|
+
row = await db.fetchrow(query, schema_name, user_id)
|
|
238
|
+
else:
|
|
239
|
+
# No user_id - only search public schemas
|
|
240
|
+
query = """
|
|
241
|
+
SELECT spec FROM schemas
|
|
242
|
+
WHERE LOWER(name) = LOWER($1)
|
|
243
|
+
AND (user_id = 'system' OR user_id IS NULL)
|
|
244
|
+
LIMIT 1
|
|
245
|
+
"""
|
|
246
|
+
row = await db.fetchrow(query, schema_name)
|
|
247
|
+
logger.debug(f"Executing schema lookup: name={schema_name}, user_id={user_id or 'public'}")
|
|
207
248
|
|
|
208
249
|
if row:
|
|
209
250
|
spec = row.get("spec")
|
|
@@ -231,74 +272,66 @@ def load_agent_schema(
|
|
|
231
272
|
enable_db_fallback: bool = True,
|
|
232
273
|
) -> dict[str, Any]:
|
|
233
274
|
"""
|
|
234
|
-
Load agent schema
|
|
275
|
+
Load agent schema with database-first priority for hot-reloading support.
|
|
235
276
|
|
|
236
277
|
Schema names are case-invariant - "Rem", "rem", "REM" all resolve to the same schema.
|
|
237
278
|
|
|
238
|
-
|
|
239
|
-
|
|
279
|
+
**IMPORTANT**: Database is checked FIRST (before filesystem) to enable hot-reloading
|
|
280
|
+
of schema updates without redeploying the application. This allows operators to
|
|
281
|
+
update schemas via `rem process ingest` and have changes take effect immediately.
|
|
240
282
|
|
|
241
283
|
Handles path resolution automatically:
|
|
242
|
-
- "rem" → searches schemas/agents/rem.yaml
|
|
243
|
-
- "moment-builder" → searches schemas/agents/core/moment-builder.yaml
|
|
244
|
-
- "
|
|
245
|
-
- "
|
|
246
|
-
- "/absolute/path.yaml" → loads directly
|
|
247
|
-
- "relative/path.yaml" → loads relative to cwd
|
|
284
|
+
- "rem" → searches database, then schemas/agents/rem.yaml
|
|
285
|
+
- "moment-builder" → searches database, then schemas/agents/core/moment-builder.yaml
|
|
286
|
+
- "/absolute/path.yaml" → loads directly from filesystem (exact paths skip database)
|
|
287
|
+
- "relative/path.yaml" → loads relative to cwd (exact paths skip database)
|
|
248
288
|
|
|
249
289
|
Search Order:
|
|
250
|
-
1.
|
|
251
|
-
2.
|
|
252
|
-
3.
|
|
253
|
-
4.
|
|
254
|
-
5. Package resources: schemas/agents/
|
|
255
|
-
6. Package resources: schemas/agents/
|
|
256
|
-
7. Package resources: schemas/
|
|
257
|
-
8. Package resources: schemas/{name}.yaml
|
|
258
|
-
9.
|
|
290
|
+
1. Exact path if it exists (absolute or relative) - skips database
|
|
291
|
+
2. Database LOOKUP: schemas table (if enable_db_fallback=True) - PREFERRED for hot-reload
|
|
292
|
+
3. Check cache (if use_cache=True and schema found in FS cache)
|
|
293
|
+
4. Custom paths from rem.register_schema_path() and SCHEMA__PATHS env var
|
|
294
|
+
5. Package resources: schemas/agents/{name}.yaml (top-level)
|
|
295
|
+
6. Package resources: schemas/agents/core/{name}.yaml
|
|
296
|
+
7. Package resources: schemas/agents/examples/{name}.yaml
|
|
297
|
+
8. Package resources: schemas/evaluators/{name}.yaml
|
|
298
|
+
9. Package resources: schemas/{name}.yaml
|
|
259
299
|
|
|
260
300
|
Args:
|
|
261
301
|
schema_name_or_path: Schema name or file path (case-invariant for names)
|
|
262
302
|
Examples: "rem-query-agent", "Contract-Analyzer", "./my-schema.yaml"
|
|
263
303
|
use_cache: If True, uses in-memory cache for filesystem schemas
|
|
264
|
-
user_id: User ID for database schema lookup
|
|
265
|
-
enable_db_fallback: If True,
|
|
304
|
+
user_id: User ID for database schema lookup
|
|
305
|
+
enable_db_fallback: If True, checks database FIRST for schema (default: True)
|
|
266
306
|
|
|
267
307
|
Returns:
|
|
268
308
|
Agent schema as dictionary
|
|
269
309
|
|
|
270
310
|
Raises:
|
|
271
|
-
FileNotFoundError: If schema not found in any search location (
|
|
311
|
+
FileNotFoundError: If schema not found in any search location (database + filesystem)
|
|
272
312
|
yaml.YAMLError: If schema file is invalid YAML
|
|
273
313
|
|
|
274
314
|
Examples:
|
|
275
|
-
>>> # Load by short name
|
|
276
|
-
>>> schema = load_agent_schema("Contract-Analyzer") #
|
|
315
|
+
>>> # Load by short name - checks database first for hot-reload support
|
|
316
|
+
>>> schema = load_agent_schema("Contract-Analyzer") # case invariant
|
|
277
317
|
>>>
|
|
278
|
-
>>> # Load from custom path (
|
|
318
|
+
>>> # Load from custom path (skips database - exact paths always use filesystem)
|
|
279
319
|
>>> schema = load_agent_schema("./my-agent.yaml")
|
|
280
320
|
>>>
|
|
281
|
-
>>> # Load evaluator schema
|
|
321
|
+
>>> # Load evaluator schema
|
|
282
322
|
>>> schema = load_agent_schema("rem-lookup-correctness")
|
|
283
|
-
>>>
|
|
284
|
-
>>> # Load custom user schema from database (case invariant)
|
|
285
|
-
>>> schema = load_agent_schema("My-Agent", user_id="user-123") # same as "my-agent"
|
|
286
323
|
"""
|
|
287
324
|
# Normalize the name for cache key (lowercase for case-invariant lookups)
|
|
288
325
|
cache_key = str(schema_name_or_path).replace('agents/', '').replace('schemas/', '').replace('evaluators/', '').replace('core/', '').replace('examples/', '').lower()
|
|
289
326
|
if cache_key.endswith('.yaml') or cache_key.endswith('.yml'):
|
|
290
327
|
cache_key = cache_key.rsplit('.', 1)[0]
|
|
291
328
|
|
|
292
|
-
# Check cache first (only for package resources, not custom paths)
|
|
293
329
|
path = Path(schema_name_or_path)
|
|
294
|
-
is_custom_path = path.exists() or '/' in str(schema_name_or_path) or '\\' in str(schema_name_or_path)
|
|
295
|
-
|
|
296
|
-
if use_cache and not is_custom_path and cache_key in _fs_schema_cache:
|
|
297
|
-
logger.debug(f"Loading schema from cache: {cache_key}")
|
|
298
|
-
return _fs_schema_cache[cache_key]
|
|
330
|
+
is_custom_path = (path.exists() and path.is_file()) or '/' in str(schema_name_or_path) or '\\' in str(schema_name_or_path)
|
|
299
331
|
|
|
300
|
-
# 1. Try exact path first (absolute or relative to cwd)
|
|
301
|
-
|
|
332
|
+
# 1. Try exact path first (absolute or relative to cwd) - must be a file, not directory
|
|
333
|
+
# Exact paths skip database lookup (explicit file reference)
|
|
334
|
+
if path.exists() and path.is_file():
|
|
302
335
|
logger.debug(f"Loading schema from exact path: {path}")
|
|
303
336
|
with open(path, "r") as f:
|
|
304
337
|
schema = yaml.safe_load(f)
|
|
@@ -306,10 +339,28 @@ def load_agent_schema(
|
|
|
306
339
|
# Don't cache custom paths (they may change)
|
|
307
340
|
return cast(dict[str, Any], schema)
|
|
308
341
|
|
|
309
|
-
# 2. Normalize name for
|
|
342
|
+
# 2. Normalize name for lookups (lowercase)
|
|
310
343
|
base_name = cache_key
|
|
311
344
|
|
|
312
|
-
# 3. Try
|
|
345
|
+
# 3. Try database FIRST (if enabled) - enables hot-reload without redeploy
|
|
346
|
+
# Database schemas are NOT cached to ensure hot-reload works immediately
|
|
347
|
+
if enable_db_fallback and not is_custom_path:
|
|
348
|
+
try:
|
|
349
|
+
logger.debug(f"Checking database for schema: {base_name} (user_id={user_id or 'public'})")
|
|
350
|
+
db_schema = _load_schema_from_database(base_name, user_id)
|
|
351
|
+
if db_schema:
|
|
352
|
+
logger.info(f"✅ Loaded schema from database: {base_name}")
|
|
353
|
+
return db_schema
|
|
354
|
+
except Exception as e:
|
|
355
|
+
logger.debug(f"Database schema lookup failed: {e}")
|
|
356
|
+
# Fall through to filesystem search
|
|
357
|
+
|
|
358
|
+
# 4. Check filesystem cache (only for package resources, not custom paths)
|
|
359
|
+
if use_cache and not is_custom_path and cache_key in _fs_schema_cache:
|
|
360
|
+
logger.debug(f"Loading schema from cache: {cache_key}")
|
|
361
|
+
return _fs_schema_cache[cache_key]
|
|
362
|
+
|
|
363
|
+
# 5. Try custom schema paths (from registry + SCHEMA__PATHS env var + auto-detected)
|
|
313
364
|
from ..registry import get_schema_paths
|
|
314
365
|
|
|
315
366
|
custom_paths = get_schema_paths()
|
|
@@ -340,7 +391,7 @@ def load_agent_schema(
|
|
|
340
391
|
# Don't cache custom paths (they may change during development)
|
|
341
392
|
return cast(dict[str, Any], schema)
|
|
342
393
|
|
|
343
|
-
#
|
|
394
|
+
# 6. Try package resources with standard search paths
|
|
344
395
|
for search_pattern in SCHEMA_SEARCH_PATHS:
|
|
345
396
|
search_path = search_pattern.format(name=base_name)
|
|
346
397
|
|
|
@@ -365,19 +416,7 @@ def load_agent_schema(
|
|
|
365
416
|
logger.debug(f"Could not load from {search_path}: {e}")
|
|
366
417
|
continue
|
|
367
418
|
|
|
368
|
-
#
|
|
369
|
-
if enable_db_fallback and user_id:
|
|
370
|
-
try:
|
|
371
|
-
logger.debug(f"Attempting database LOOKUP for schema: {base_name} (user_id={user_id})")
|
|
372
|
-
db_schema = _load_schema_from_database(base_name, user_id)
|
|
373
|
-
if db_schema:
|
|
374
|
-
logger.info(f"✅ Loaded schema from database: {base_name} (user_id={user_id})")
|
|
375
|
-
return db_schema
|
|
376
|
-
except Exception as e:
|
|
377
|
-
logger.debug(f"Database schema lookup failed: {e}")
|
|
378
|
-
# Fall through to error below
|
|
379
|
-
|
|
380
|
-
# 6. Schema not found in any location
|
|
419
|
+
# 7. Schema not found in any location
|
|
381
420
|
searched_paths = [pattern.format(name=base_name) for pattern in SCHEMA_SEARCH_PATHS]
|
|
382
421
|
|
|
383
422
|
custom_paths_note = ""
|
|
@@ -387,9 +426,9 @@ def load_agent_schema(
|
|
|
387
426
|
db_search_note = ""
|
|
388
427
|
if enable_db_fallback:
|
|
389
428
|
if user_id:
|
|
390
|
-
db_search_note = f"\n - Database: LOOKUP '{base_name}' FROM schemas WHERE user_id
|
|
429
|
+
db_search_note = f"\n - Database: LOOKUP '{base_name}' FROM schemas WHERE user_id IN ('{user_id}', 'system', NULL) (no match)"
|
|
391
430
|
else:
|
|
392
|
-
db_search_note = "\n - Database:
|
|
431
|
+
db_search_note = f"\n - Database: LOOKUP '{base_name}' FROM schemas WHERE user_id IN ('system', NULL) (no match)"
|
|
393
432
|
|
|
394
433
|
raise FileNotFoundError(
|
|
395
434
|
f"Schema not found: {schema_name_or_path}\n"
|
|
@@ -405,18 +444,21 @@ async def load_agent_schema_async(
|
|
|
405
444
|
schema_name_or_path: str,
|
|
406
445
|
user_id: str | None = None,
|
|
407
446
|
db=None,
|
|
447
|
+
enable_db_fallback: bool = True,
|
|
408
448
|
) -> dict[str, Any]:
|
|
409
449
|
"""
|
|
410
|
-
Async version of load_agent_schema
|
|
450
|
+
Async version of load_agent_schema with database-first priority.
|
|
411
451
|
|
|
412
452
|
Schema names are case-invariant - "MyAgent", "myagent", "MYAGENT" all resolve to the same schema.
|
|
413
453
|
|
|
414
|
-
|
|
454
|
+
**IMPORTANT**: Database is checked FIRST (before filesystem) to enable hot-reloading
|
|
455
|
+
of schema updates without redeploying the application.
|
|
415
456
|
|
|
416
457
|
Args:
|
|
417
458
|
schema_name_or_path: Schema name or file path (case-invariant for names)
|
|
418
459
|
user_id: User ID for database schema lookup
|
|
419
460
|
db: Optional existing PostgresService connection (if None, will create one)
|
|
461
|
+
enable_db_fallback: If True, checks database FIRST for schema (default: True)
|
|
420
462
|
|
|
421
463
|
Returns:
|
|
422
464
|
Agent schema as dictionary
|
|
@@ -424,7 +466,6 @@ async def load_agent_schema_async(
|
|
|
424
466
|
Raises:
|
|
425
467
|
FileNotFoundError: If schema not found
|
|
426
468
|
"""
|
|
427
|
-
# First try filesystem search (sync operations are fine)
|
|
428
469
|
path = Path(schema_name_or_path)
|
|
429
470
|
|
|
430
471
|
# Normalize the name for cache key (lowercase for case-invariant lookups)
|
|
@@ -432,15 +473,10 @@ async def load_agent_schema_async(
|
|
|
432
473
|
if cache_key.endswith('.yaml') or cache_key.endswith('.yml'):
|
|
433
474
|
cache_key = cache_key.rsplit('.', 1)[0]
|
|
434
475
|
|
|
435
|
-
is_custom_path = path.exists() or '/' in str(schema_name_or_path) or '\\' in str(schema_name_or_path)
|
|
436
|
-
|
|
437
|
-
# Check cache
|
|
438
|
-
if not is_custom_path and cache_key in _fs_schema_cache:
|
|
439
|
-
logger.debug(f"Loading schema from cache: {cache_key}")
|
|
440
|
-
return _fs_schema_cache[cache_key]
|
|
476
|
+
is_custom_path = (path.exists() and path.is_file()) or '/' in str(schema_name_or_path) or '\\' in str(schema_name_or_path)
|
|
441
477
|
|
|
442
|
-
# Try exact path
|
|
443
|
-
if path.exists():
|
|
478
|
+
# 1. Try exact path first (skips database - explicit file reference)
|
|
479
|
+
if path.exists() and path.is_file():
|
|
444
480
|
logger.debug(f"Loading schema from exact path: {path}")
|
|
445
481
|
with open(path, "r") as f:
|
|
446
482
|
schema = yaml.safe_load(f)
|
|
@@ -448,7 +484,60 @@ async def load_agent_schema_async(
|
|
|
448
484
|
|
|
449
485
|
base_name = cache_key
|
|
450
486
|
|
|
451
|
-
# Try
|
|
487
|
+
# 2. Try database FIRST (if enabled) - enables hot-reload without redeploy
|
|
488
|
+
if enable_db_fallback and not is_custom_path:
|
|
489
|
+
# Check DB schema cache first (TTL-based)
|
|
490
|
+
cached_schema = _get_cached_db_schema(base_name, user_id)
|
|
491
|
+
if cached_schema is not None:
|
|
492
|
+
logger.info(f"✅ Loaded schema from cache: {base_name}")
|
|
493
|
+
return cached_schema
|
|
494
|
+
|
|
495
|
+
# Cache miss - query database
|
|
496
|
+
from rem.services.postgres import get_postgres_service
|
|
497
|
+
|
|
498
|
+
should_disconnect = False
|
|
499
|
+
if db is None:
|
|
500
|
+
db = get_postgres_service()
|
|
501
|
+
if db:
|
|
502
|
+
await db.connect()
|
|
503
|
+
should_disconnect = True
|
|
504
|
+
|
|
505
|
+
if db:
|
|
506
|
+
try:
|
|
507
|
+
if user_id:
|
|
508
|
+
query = """
|
|
509
|
+
SELECT spec FROM schemas
|
|
510
|
+
WHERE LOWER(name) = LOWER($1)
|
|
511
|
+
AND (user_id = $2 OR user_id = 'system' OR user_id IS NULL)
|
|
512
|
+
LIMIT 1
|
|
513
|
+
"""
|
|
514
|
+
row = await db.fetchrow(query, base_name, user_id)
|
|
515
|
+
else:
|
|
516
|
+
# No user_id - only search public schemas
|
|
517
|
+
query = """
|
|
518
|
+
SELECT spec FROM schemas
|
|
519
|
+
WHERE LOWER(name) = LOWER($1)
|
|
520
|
+
AND (user_id = 'system' OR user_id IS NULL)
|
|
521
|
+
LIMIT 1
|
|
522
|
+
"""
|
|
523
|
+
row = await db.fetchrow(query, base_name)
|
|
524
|
+
if row:
|
|
525
|
+
spec = row.get("spec")
|
|
526
|
+
if spec and isinstance(spec, dict):
|
|
527
|
+
# Cache the schema for future requests
|
|
528
|
+
_cache_db_schema(base_name, user_id, spec)
|
|
529
|
+
logger.info(f"✅ Loaded schema from database: {base_name}")
|
|
530
|
+
return spec
|
|
531
|
+
finally:
|
|
532
|
+
if should_disconnect:
|
|
533
|
+
await db.disconnect()
|
|
534
|
+
|
|
535
|
+
# 3. Check filesystem cache
|
|
536
|
+
if not is_custom_path and cache_key in _fs_schema_cache:
|
|
537
|
+
logger.debug(f"Loading schema from cache: {cache_key}")
|
|
538
|
+
return _fs_schema_cache[cache_key]
|
|
539
|
+
|
|
540
|
+
# 4. Try custom schema paths (from registry + SCHEMA__PATHS env var + auto-detected)
|
|
452
541
|
from ..registry import get_schema_paths
|
|
453
542
|
custom_paths = get_schema_paths()
|
|
454
543
|
|
|
@@ -470,7 +559,7 @@ async def load_agent_schema_async(
|
|
|
470
559
|
schema = yaml.safe_load(f)
|
|
471
560
|
return cast(dict[str, Any], schema)
|
|
472
561
|
|
|
473
|
-
# Try package resources
|
|
562
|
+
# 5. Try package resources
|
|
474
563
|
for search_pattern in SCHEMA_SEARCH_PATHS:
|
|
475
564
|
search_path = search_pattern.format(name=base_name)
|
|
476
565
|
try:
|
|
@@ -484,35 +573,6 @@ async def load_agent_schema_async(
|
|
|
484
573
|
except Exception:
|
|
485
574
|
continue
|
|
486
575
|
|
|
487
|
-
# Try database lookup
|
|
488
|
-
if user_id:
|
|
489
|
-
from rem.services.postgres import get_postgres_service
|
|
490
|
-
|
|
491
|
-
should_disconnect = False
|
|
492
|
-
if db is None:
|
|
493
|
-
db = get_postgres_service()
|
|
494
|
-
if db:
|
|
495
|
-
await db.connect()
|
|
496
|
-
should_disconnect = True
|
|
497
|
-
|
|
498
|
-
if db:
|
|
499
|
-
try:
|
|
500
|
-
query = """
|
|
501
|
-
SELECT spec FROM schemas
|
|
502
|
-
WHERE LOWER(name) = LOWER($1)
|
|
503
|
-
AND (user_id = $2 OR user_id = 'system' OR user_id IS NULL)
|
|
504
|
-
LIMIT 1
|
|
505
|
-
"""
|
|
506
|
-
row = await db.fetchrow(query, base_name, user_id)
|
|
507
|
-
if row:
|
|
508
|
-
spec = row.get("spec")
|
|
509
|
-
if spec and isinstance(spec, dict):
|
|
510
|
-
logger.info(f"✅ Loaded schema from database: {base_name}")
|
|
511
|
-
return spec
|
|
512
|
-
finally:
|
|
513
|
-
if should_disconnect:
|
|
514
|
-
await db.disconnect()
|
|
515
|
-
|
|
516
576
|
# Not found
|
|
517
577
|
raise FileNotFoundError(f"Schema not found: {schema_name_or_path}")
|
|
518
578
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remdb
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.258
|
|
4
4
|
Summary: Resources Entities Moments - Bio-inspired memory system for agentic AI workloads
|
|
5
5
|
Project-URL: Homepage, https://github.com/Percolation-Labs/reminiscent
|
|
6
6
|
Project-URL: Documentation, https://github.com/Percolation-Labs/reminiscent/blob/main/README.md
|
|
@@ -28,7 +28,7 @@ Requires-Dist: gitpython>=3.1.45
|
|
|
28
28
|
Requires-Dist: hypercorn>=0.17.0
|
|
29
29
|
Requires-Dist: itsdangerous>=2.0.0
|
|
30
30
|
Requires-Dist: json-schema-to-pydantic>=0.2.0
|
|
31
|
-
Requires-Dist: kreuzberg
|
|
31
|
+
Requires-Dist: kreuzberg>=4.0.5
|
|
32
32
|
Requires-Dist: loguru>=0.7.0
|
|
33
33
|
Requires-Dist: openinference-instrumentation-pydantic-ai>=0.1.0
|
|
34
34
|
Requires-Dist: opentelemetry-api>=1.28.0
|
|
@@ -39,7 +39,7 @@ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.49b0
|
|
|
39
39
|
Requires-Dist: opentelemetry-instrumentation>=0.49b0
|
|
40
40
|
Requires-Dist: opentelemetry-sdk>=1.28.0
|
|
41
41
|
Requires-Dist: psycopg[binary]>=3.2.0
|
|
42
|
-
Requires-Dist: pydantic-ai>=0.0
|
|
42
|
+
Requires-Dist: pydantic-ai>=1.0.0
|
|
43
43
|
Requires-Dist: pydantic-settings>=2.6.0
|
|
44
44
|
Requires-Dist: pydantic>=2.10.0
|
|
45
45
|
Requires-Dist: pydub>=0.25.0
|
|
@@ -1300,15 +1300,16 @@ FuzzyQuery ::= FUZZY <text:string> [THRESHOLD <t:float>] [LIMIT <n:int>]
|
|
|
1300
1300
|
available : Stage 1+
|
|
1301
1301
|
example : FUZZY "sara" THRESHOLD 0.5 LIMIT 10
|
|
1302
1302
|
|
|
1303
|
-
SearchQuery ::= SEARCH <text:string> [TABLE <table:string>] [WHERE <clause:string>] [LIMIT <n:int>]
|
|
1303
|
+
SearchQuery ::= SEARCH <text:string> [IN|TABLE <table:string>] [WHERE <clause:string>] [LIMIT <n:int>]
|
|
1304
1304
|
text : Semantic query text
|
|
1305
|
-
table : Target table (default: "resources")
|
|
1305
|
+
table : Target table (default: "resources"). Use IN or TABLE keyword.
|
|
1306
1306
|
clause : Optional PostgreSQL WHERE clause for hybrid filtering (combines vector + structured)
|
|
1307
1307
|
limit : Max results (default: 10)
|
|
1308
1308
|
performance : Indexed (pgvector)
|
|
1309
1309
|
available : Stage 3+
|
|
1310
1310
|
examples :
|
|
1311
|
-
- SEARCH "database migration"
|
|
1311
|
+
- SEARCH "database migration" IN resources LIMIT 10
|
|
1312
|
+
- SEARCH "parcel delivery" IN ontologies
|
|
1312
1313
|
- SEARCH "team discussion" TABLE moments WHERE "moment_type='meeting'" LIMIT 5
|
|
1313
1314
|
- SEARCH "project updates" WHERE "created_at >= '2024-01-01'" LIMIT 20
|
|
1314
1315
|
- SEARCH "AI research" WHERE "tags @> ARRAY['machine-learning']" LIMIT 10
|