remdb 0.3.103__py3-none-any.whl → 0.3.141__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.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (74) hide show
  1. rem/agentic/agents/sse_simulator.py +2 -0
  2. rem/agentic/context.py +51 -27
  3. rem/agentic/mcp/tool_wrapper.py +155 -18
  4. rem/agentic/otel/setup.py +93 -4
  5. rem/agentic/providers/phoenix.py +371 -108
  6. rem/agentic/providers/pydantic_ai.py +195 -46
  7. rem/agentic/schema.py +361 -21
  8. rem/agentic/tools/rem_tools.py +3 -3
  9. rem/api/main.py +85 -16
  10. rem/api/mcp_router/resources.py +1 -1
  11. rem/api/mcp_router/server.py +18 -4
  12. rem/api/mcp_router/tools.py +394 -16
  13. rem/api/routers/admin.py +218 -1
  14. rem/api/routers/chat/completions.py +280 -7
  15. rem/api/routers/chat/models.py +81 -7
  16. rem/api/routers/chat/otel_utils.py +33 -0
  17. rem/api/routers/chat/sse_events.py +17 -1
  18. rem/api/routers/chat/streaming.py +177 -3
  19. rem/api/routers/feedback.py +142 -329
  20. rem/api/routers/query.py +360 -0
  21. rem/api/routers/shared_sessions.py +13 -13
  22. rem/cli/commands/README.md +237 -64
  23. rem/cli/commands/cluster.py +1808 -0
  24. rem/cli/commands/configure.py +4 -7
  25. rem/cli/commands/db.py +354 -143
  26. rem/cli/commands/experiments.py +436 -30
  27. rem/cli/commands/process.py +14 -8
  28. rem/cli/commands/schema.py +92 -45
  29. rem/cli/commands/session.py +336 -0
  30. rem/cli/dreaming.py +2 -2
  31. rem/cli/main.py +29 -6
  32. rem/config.py +8 -1
  33. rem/models/core/experiment.py +54 -0
  34. rem/models/core/rem_query.py +5 -2
  35. rem/models/entities/ontology.py +1 -1
  36. rem/models/entities/ontology_config.py +1 -1
  37. rem/models/entities/shared_session.py +2 -28
  38. rem/registry.py +10 -4
  39. rem/schemas/agents/examples/contract-analyzer.yaml +1 -1
  40. rem/schemas/agents/examples/contract-extractor.yaml +1 -1
  41. rem/schemas/agents/examples/cv-parser.yaml +1 -1
  42. rem/services/content/service.py +30 -8
  43. rem/services/embeddings/api.py +4 -4
  44. rem/services/embeddings/worker.py +16 -16
  45. rem/services/phoenix/client.py +59 -18
  46. rem/services/postgres/README.md +151 -26
  47. rem/services/postgres/__init__.py +2 -1
  48. rem/services/postgres/diff_service.py +531 -0
  49. rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
  50. rem/services/postgres/schema_generator.py +205 -4
  51. rem/services/postgres/service.py +6 -6
  52. rem/services/rem/parser.py +44 -9
  53. rem/services/rem/service.py +36 -2
  54. rem/services/session/compression.py +7 -0
  55. rem/services/session/reload.py +1 -1
  56. rem/settings.py +288 -16
  57. rem/sql/background_indexes.sql +19 -24
  58. rem/sql/migrations/001_install.sql +252 -69
  59. rem/sql/migrations/002_install_models.sql +2197 -619
  60. rem/sql/migrations/003_optional_extensions.sql +326 -0
  61. rem/sql/migrations/004_cache_system.sql +548 -0
  62. rem/utils/__init__.py +18 -0
  63. rem/utils/date_utils.py +2 -2
  64. rem/utils/schema_loader.py +110 -15
  65. rem/utils/sql_paths.py +146 -0
  66. rem/utils/vision.py +1 -1
  67. rem/workers/__init__.py +3 -1
  68. rem/workers/db_listener.py +579 -0
  69. rem/workers/unlogged_maintainer.py +463 -0
  70. {remdb-0.3.103.dist-info → remdb-0.3.141.dist-info}/METADATA +300 -215
  71. {remdb-0.3.103.dist-info → remdb-0.3.141.dist-info}/RECORD +73 -64
  72. rem/sql/migrations/003_seed_default_user.sql +0 -48
  73. {remdb-0.3.103.dist-info → remdb-0.3.141.dist-info}/WHEEL +0 -0
  74. {remdb-0.3.103.dist-info → remdb-0.3.141.dist-info}/entry_points.txt +0 -0
@@ -195,6 +195,8 @@ def load_agent_schema(
195
195
  """
196
196
  Load agent schema from YAML file with unified search logic and caching.
197
197
 
198
+ Schema names are case-invariant - "Rem", "rem", "REM" all resolve to the same schema.
199
+
198
200
  Filesystem schemas are cached indefinitely (immutable, versioned with code).
199
201
  Database schemas (future) will be cached with TTL for invalidation.
200
202
 
@@ -218,8 +220,8 @@ def load_agent_schema(
218
220
  9. Database LOOKUP: schemas table (if enable_db_fallback=True and user_id provided)
219
221
 
220
222
  Args:
221
- schema_name_or_path: Schema name or file path
222
- Examples: "rem-query-agent", "contract-analyzer", "./my-schema.yaml"
223
+ schema_name_or_path: Schema name or file path (case-invariant for names)
224
+ Examples: "rem-query-agent", "Contract-Analyzer", "./my-schema.yaml"
223
225
  use_cache: If True, uses in-memory cache for filesystem schemas
224
226
  user_id: User ID for database schema lookup (required for DB fallback)
225
227
  enable_db_fallback: If True, falls back to database LOOKUP when file not found
@@ -232,8 +234,8 @@ def load_agent_schema(
232
234
  yaml.YAMLError: If schema file is invalid YAML
233
235
 
234
236
  Examples:
235
- >>> # Load by short name (cached after first load)
236
- >>> schema = load_agent_schema("contract-analyzer")
237
+ >>> # Load by short name (cached after first load) - case invariant
238
+ >>> schema = load_agent_schema("Contract-Analyzer") # same as "contract-analyzer"
237
239
  >>>
238
240
  >>> # Load from custom path (not cached - custom paths may change)
239
241
  >>> schema = load_agent_schema("./my-agent.yaml")
@@ -241,11 +243,11 @@ def load_agent_schema(
241
243
  >>> # Load evaluator schema (cached)
242
244
  >>> schema = load_agent_schema("rem-lookup-correctness")
243
245
  >>>
244
- >>> # Load custom user schema from database
245
- >>> schema = load_agent_schema("my-custom-agent", user_id="user-123")
246
+ >>> # Load custom user schema from database (case invariant)
247
+ >>> schema = load_agent_schema("My-Agent", user_id="user-123") # same as "my-agent"
246
248
  """
247
- # Normalize the name for cache key
248
- cache_key = str(schema_name_or_path).replace('agents/', '').replace('schemas/', '').replace('evaluators/', '').replace('core/', '').replace('examples/', '')
249
+ # Normalize the name for cache key (lowercase for case-invariant lookups)
250
+ cache_key = str(schema_name_or_path).replace('agents/', '').replace('schemas/', '').replace('evaluators/', '').replace('core/', '').replace('examples/', '').lower()
249
251
  if cache_key.endswith('.yaml') or cache_key.endswith('.yml'):
250
252
  cache_key = cache_key.rsplit('.', 1)[0]
251
253
 
@@ -266,13 +268,23 @@ def load_agent_schema(
266
268
  # Don't cache custom paths (they may change)
267
269
  return cast(dict[str, Any], schema)
268
270
 
269
- # 2. Normalize name for package resource search
271
+ # 2. Normalize name for package resource search (lowercase)
270
272
  base_name = cache_key
271
273
 
272
- # 3. Try custom schema paths (from registry + SCHEMA__PATHS env var)
274
+ # 3. Try custom schema paths (from registry + SCHEMA__PATHS env var + auto-detected)
273
275
  from ..registry import get_schema_paths
274
276
 
275
277
  custom_paths = get_schema_paths()
278
+
279
+ # Auto-detect local folders if they exist (convention over configuration)
280
+ auto_detect_folders = ["./agents", "./schemas", "./evaluators"]
281
+ for auto_folder in auto_detect_folders:
282
+ auto_path = Path(auto_folder)
283
+ if auto_path.exists() and auto_path.is_dir():
284
+ resolved = str(auto_path.resolve())
285
+ if resolved not in custom_paths:
286
+ custom_paths.insert(0, resolved)
287
+ logger.debug(f"Auto-detected schema directory: {auto_folder}")
276
288
  for custom_dir in custom_paths:
277
289
  # Try various patterns within each custom directory
278
290
  for pattern in [
@@ -359,10 +371,12 @@ async def load_agent_schema_async(
359
371
  """
360
372
  Async version of load_agent_schema for use in async contexts.
361
373
 
374
+ Schema names are case-invariant - "MyAgent", "myagent", "MYAGENT" all resolve to the same schema.
375
+
362
376
  This version accepts an existing database connection to avoid creating new connections.
363
377
 
364
378
  Args:
365
- schema_name_or_path: Schema name or file path
379
+ schema_name_or_path: Schema name or file path (case-invariant for names)
366
380
  user_id: User ID for database schema lookup
367
381
  db: Optional existing PostgresService connection (if None, will create one)
368
382
 
@@ -375,8 +389,8 @@ async def load_agent_schema_async(
375
389
  # First try filesystem search (sync operations are fine)
376
390
  path = Path(schema_name_or_path)
377
391
 
378
- # Normalize the name for cache key
379
- cache_key = str(schema_name_or_path).replace('agents/', '').replace('schemas/', '').replace('evaluators/', '').replace('core/', '').replace('examples/', '')
392
+ # Normalize the name for cache key (lowercase for case-invariant lookups)
393
+ cache_key = str(schema_name_or_path).replace('agents/', '').replace('schemas/', '').replace('evaluators/', '').replace('core/', '').replace('examples/', '').lower()
380
394
  if cache_key.endswith('.yaml') or cache_key.endswith('.yml'):
381
395
  cache_key = cache_key.rsplit('.', 1)[0]
382
396
 
@@ -396,9 +410,20 @@ async def load_agent_schema_async(
396
410
 
397
411
  base_name = cache_key
398
412
 
399
- # Try custom schema paths
413
+ # Try custom schema paths (from registry + SCHEMA__PATHS env var + auto-detected)
400
414
  from ..registry import get_schema_paths
401
415
  custom_paths = get_schema_paths()
416
+
417
+ # Auto-detect local folders if they exist (convention over configuration)
418
+ auto_detect_folders = ["./agents", "./schemas", "./evaluators"]
419
+ for auto_folder in auto_detect_folders:
420
+ auto_path = Path(auto_folder)
421
+ if auto_path.exists() and auto_path.is_dir():
422
+ resolved = str(auto_path.resolve())
423
+ if resolved not in custom_paths:
424
+ custom_paths.insert(0, resolved)
425
+ logger.debug(f"Auto-detected schema directory: {auto_folder}")
426
+
402
427
  for custom_dir in custom_paths:
403
428
  for pattern in [f"{base_name}.yaml", f"{base_name}.yml", f"agents/{base_name}.yaml"]:
404
429
  custom_path = Path(custom_dir) / pattern
@@ -437,7 +462,7 @@ async def load_agent_schema_async(
437
462
  query = """
438
463
  SELECT spec FROM schemas
439
464
  WHERE LOWER(name) = LOWER($1)
440
- AND (user_id = $2 OR user_id = 'system')
465
+ AND (user_id = $2 OR user_id = 'system' OR user_id IS NULL)
441
466
  LIMIT 1
442
467
  """
443
468
  row = await db.fetchrow(query, base_name, user_id)
@@ -486,3 +511,73 @@ def validate_agent_schema(schema: dict[str, Any]) -> bool:
486
511
 
487
512
  logger.debug("Schema validation passed")
488
513
  return True
514
+
515
+
516
+ def get_evaluator_schema_path(evaluator_name: str) -> Path | None:
517
+ """
518
+ Find the file path to an evaluator schema.
519
+
520
+ Searches standard locations for the evaluator schema YAML file:
521
+ - ./evaluators/{name}.yaml (local project)
522
+ - Custom schema paths from registry
523
+ - Package resources: schemas/evaluators/{name}.yaml
524
+
525
+ Args:
526
+ evaluator_name: Name of the evaluator (e.g., "mental-health-classifier")
527
+
528
+ Returns:
529
+ Path to the evaluator schema file, or None if not found
530
+
531
+ Example:
532
+ >>> path = get_evaluator_schema_path("mental-health-classifier")
533
+ >>> if path:
534
+ ... print(f"Found evaluator at: {path}")
535
+ """
536
+ from ..registry import get_schema_paths
537
+
538
+ base_name = evaluator_name.lower().replace('.yaml', '').replace('.yml', '')
539
+
540
+ # 1. Try custom schema paths (from registry + auto-detected)
541
+ custom_paths = get_schema_paths()
542
+
543
+ # Auto-detect local folders
544
+ auto_detect_folders = ["./evaluators", "./schemas", "./agents"]
545
+ for auto_folder in auto_detect_folders:
546
+ auto_path = Path(auto_folder)
547
+ if auto_path.exists() and auto_path.is_dir():
548
+ resolved = str(auto_path.resolve())
549
+ if resolved not in custom_paths:
550
+ custom_paths.insert(0, resolved)
551
+
552
+ for custom_dir in custom_paths:
553
+ # Try various patterns within each custom directory
554
+ for pattern in [
555
+ f"{base_name}.yaml",
556
+ f"{base_name}.yml",
557
+ f"evaluators/{base_name}.yaml",
558
+ ]:
559
+ custom_path = Path(custom_dir) / pattern
560
+ if custom_path.exists():
561
+ logger.debug(f"Found evaluator schema: {custom_path}")
562
+ return custom_path
563
+
564
+ # 2. Try package resources
565
+ evaluator_search_paths = [
566
+ f"schemas/evaluators/{base_name}.yaml",
567
+ f"schemas/evaluators/rem/{base_name}.yaml",
568
+ ]
569
+
570
+ for search_path in evaluator_search_paths:
571
+ try:
572
+ schema_ref = importlib.resources.files("rem") / search_path
573
+ schema_path = Path(str(schema_ref))
574
+
575
+ if schema_path.exists():
576
+ logger.debug(f"Found evaluator schema in package: {schema_path}")
577
+ return schema_path
578
+ except Exception as e:
579
+ logger.debug(f"Could not check {search_path}: {e}")
580
+ continue
581
+
582
+ logger.warning(f"Evaluator schema not found: {evaluator_name}")
583
+ return None
rem/utils/sql_paths.py ADDED
@@ -0,0 +1,146 @@
1
+ """Utilities for resolving SQL file paths.
2
+
3
+ Handles package SQL directory resolution and user migrations.
4
+
5
+ Convention for user migrations:
6
+ Place custom SQL files in `./sql/migrations/` relative to your project root.
7
+ Files should be numbered (e.g., `100_custom_table.sql`) to control execution order.
8
+ Package migrations (001-099) run first, then user migrations (100+).
9
+ """
10
+
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+ import importlib.resources
14
+
15
+ # Convention: Default location for user-maintained migrations
16
+ USER_SQL_DIR_CONVENTION = "sql"
17
+
18
+
19
+ def get_package_sql_dir() -> Path:
20
+ """Get the SQL directory from the installed rem package.
21
+
22
+ Returns:
23
+ Path to the package's sql directory
24
+
25
+ Raises:
26
+ FileNotFoundError: If the SQL directory cannot be found
27
+ """
28
+ try:
29
+ # Use importlib.resources for Python 3.9+
30
+ sql_ref = importlib.resources.files("rem") / "sql"
31
+ package_sql = Path(str(sql_ref))
32
+ if package_sql.exists():
33
+ return package_sql
34
+ except (AttributeError, TypeError):
35
+ pass
36
+
37
+ # Fallback: use __file__ to find package location
38
+ try:
39
+ import rem
40
+ package_sql = Path(rem.__file__).parent / "sql"
41
+ if package_sql.exists():
42
+ return package_sql
43
+ except (ImportError, AttributeError):
44
+ pass
45
+
46
+ # Development fallback: check relative to cwd
47
+ dev_sql = Path("src/rem/sql")
48
+ if dev_sql.exists():
49
+ return dev_sql
50
+
51
+ raise FileNotFoundError(
52
+ "Could not locate rem SQL directory. "
53
+ "Ensure remdb is properly installed or run from the source directory."
54
+ )
55
+
56
+
57
+ def get_package_migrations_dir() -> Path:
58
+ """Get the migrations directory from the installed rem package.
59
+
60
+ Returns:
61
+ Path to the package's migrations directory
62
+ """
63
+ return get_package_sql_dir() / "migrations"
64
+
65
+
66
+ def get_user_sql_dir() -> Optional[Path]:
67
+ """Get the conventional user SQL directory if it exists.
68
+
69
+ Looks for `./sql/` relative to the current working directory.
70
+ This follows the convention for user-maintained migrations.
71
+
72
+ Returns:
73
+ Path to user sql directory if it exists, None otherwise
74
+ """
75
+ user_sql = Path.cwd() / USER_SQL_DIR_CONVENTION
76
+ if user_sql.exists() and user_sql.is_dir():
77
+ return user_sql
78
+ return None
79
+
80
+
81
+ def list_package_migrations() -> List[Path]:
82
+ """List all migration files in the package.
83
+
84
+ Returns:
85
+ Sorted list of migration file paths
86
+ """
87
+ try:
88
+ migrations_dir = get_package_migrations_dir()
89
+ if migrations_dir.exists():
90
+ return sorted(
91
+ f for f in migrations_dir.glob("*.sql")
92
+ if f.name[0].isdigit() # Only numbered migrations
93
+ )
94
+ except FileNotFoundError:
95
+ pass
96
+
97
+ return []
98
+
99
+
100
+ def list_user_migrations() -> List[Path]:
101
+ """List all migration files in the user's sql/migrations directory.
102
+
103
+ Returns:
104
+ Sorted list of user migration file paths
105
+ """
106
+ user_sql = get_user_sql_dir()
107
+ if user_sql:
108
+ migrations_dir = user_sql / "migrations"
109
+ if migrations_dir.exists():
110
+ return sorted(
111
+ f for f in migrations_dir.glob("*.sql")
112
+ if f.name[0].isdigit() # Only numbered migrations
113
+ )
114
+ return []
115
+
116
+
117
+ def list_all_migrations() -> List[Path]:
118
+ """List all migration files from package and user directories.
119
+
120
+ Collects migrations from:
121
+ 1. Package migrations directory
122
+ 2. User directory (./sql/migrations/) if it exists
123
+
124
+ Files are sorted by name, so use numbered prefixes to control order:
125
+ - 001-099: Reserved for package migrations
126
+ - 100+: Recommended for user migrations
127
+
128
+ Returns:
129
+ Sorted list of all migration file paths (by filename)
130
+ """
131
+ all_migrations = []
132
+ seen_names = set()
133
+
134
+ # Package migrations first
135
+ for f in list_package_migrations():
136
+ if f.name not in seen_names:
137
+ all_migrations.append(f)
138
+ seen_names.add(f.name)
139
+
140
+ # User migrations second
141
+ for f in list_user_migrations():
142
+ if f.name not in seen_names:
143
+ all_migrations.append(f)
144
+ seen_names.add(f.name)
145
+
146
+ return sorted(all_migrations, key=lambda p: p.name)
rem/utils/vision.py CHANGED
@@ -106,7 +106,7 @@ class ImageAnalyzer:
106
106
  elif provider == VisionProvider.GEMINI:
107
107
  model = "gemini-2.0-flash-exp"
108
108
  elif provider == VisionProvider.OPENAI:
109
- model = "gpt-4o"
109
+ model = "gpt-4.1"
110
110
 
111
111
  self.model = model
112
112
  self.base_url = base_url
rem/workers/__init__.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """Background workers for processing tasks."""
2
2
 
3
+ from .db_listener import DBListener
3
4
  from .sqs_file_processor import SQSFileProcessor
5
+ from .unlogged_maintainer import UnloggedMaintainer
4
6
 
5
- __all__ = ["SQSFileProcessor"]
7
+ __all__ = ["DBListener", "SQSFileProcessor", "UnloggedMaintainer"]