remdb 0.3.172__py3-none-any.whl → 0.3.223__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 (57) hide show
  1. rem/agentic/README.md +262 -2
  2. rem/agentic/context.py +173 -0
  3. rem/agentic/context_builder.py +12 -2
  4. rem/agentic/mcp/tool_wrapper.py +39 -16
  5. rem/agentic/providers/pydantic_ai.py +46 -43
  6. rem/agentic/schema.py +2 -2
  7. rem/agentic/tools/rem_tools.py +11 -0
  8. rem/api/main.py +1 -1
  9. rem/api/mcp_router/resources.py +64 -8
  10. rem/api/mcp_router/server.py +31 -24
  11. rem/api/mcp_router/tools.py +621 -166
  12. rem/api/routers/admin.py +30 -4
  13. rem/api/routers/auth.py +114 -15
  14. rem/api/routers/chat/completions.py +66 -18
  15. rem/api/routers/chat/sse_events.py +7 -3
  16. rem/api/routers/chat/streaming.py +254 -22
  17. rem/api/routers/common.py +18 -0
  18. rem/api/routers/dev.py +7 -1
  19. rem/api/routers/feedback.py +9 -1
  20. rem/api/routers/messages.py +176 -38
  21. rem/api/routers/models.py +9 -1
  22. rem/api/routers/query.py +12 -1
  23. rem/api/routers/shared_sessions.py +16 -0
  24. rem/auth/jwt.py +19 -4
  25. rem/auth/middleware.py +42 -28
  26. rem/cli/README.md +62 -0
  27. rem/cli/commands/ask.py +1 -1
  28. rem/cli/commands/db.py +148 -70
  29. rem/cli/commands/process.py +171 -43
  30. rem/models/entities/ontology.py +91 -101
  31. rem/schemas/agents/rem.yaml +1 -1
  32. rem/services/content/service.py +18 -5
  33. rem/services/email/service.py +11 -2
  34. rem/services/embeddings/worker.py +26 -12
  35. rem/services/postgres/__init__.py +28 -3
  36. rem/services/postgres/diff_service.py +57 -5
  37. rem/services/postgres/programmable_diff_service.py +635 -0
  38. rem/services/postgres/pydantic_to_sqlalchemy.py +2 -2
  39. rem/services/postgres/register_type.py +12 -11
  40. rem/services/postgres/repository.py +46 -25
  41. rem/services/postgres/schema_generator.py +5 -5
  42. rem/services/postgres/sql_builder.py +6 -5
  43. rem/services/session/__init__.py +8 -1
  44. rem/services/session/compression.py +40 -2
  45. rem/services/session/pydantic_messages.py +276 -0
  46. rem/settings.py +28 -0
  47. rem/sql/background_indexes.sql +5 -0
  48. rem/sql/migrations/001_install.sql +157 -10
  49. rem/sql/migrations/002_install_models.sql +160 -132
  50. rem/sql/migrations/004_cache_system.sql +7 -275
  51. rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
  52. rem/utils/model_helpers.py +101 -0
  53. rem/utils/schema_loader.py +6 -6
  54. {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/METADATA +1 -1
  55. {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/RECORD +57 -53
  56. {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/WHEEL +0 -0
  57. {remdb-0.3.172.dist-info → remdb-0.3.223.dist-info}/entry_points.txt +0 -0
@@ -579,60 +579,62 @@ async def create_agent(
579
579
  # Extract schema fields using typed helpers
580
580
  from ..schema import get_system_prompt, get_metadata
581
581
 
582
- # Track whether mcp_servers was explicitly configured (even if empty)
583
- mcp_servers_explicitly_set = False
584
-
585
582
  if agent_schema:
586
583
  system_prompt = get_system_prompt(agent_schema)
587
584
  metadata = get_metadata(agent_schema)
588
- # Check if mcp_servers was explicitly set (could be empty list to disable)
589
- if hasattr(metadata, 'mcp_servers') and metadata.mcp_servers is not None:
590
- mcp_server_configs = [s.model_dump() for s in metadata.mcp_servers]
591
- mcp_servers_explicitly_set = True
592
- else:
593
- mcp_server_configs = []
594
585
  resource_configs = metadata.resources if hasattr(metadata, 'resources') else []
595
586
 
587
+ # DEPRECATED: mcp_servers in agent schemas is ignored
588
+ # MCP servers are now always auto-detected at the application level
589
+ if hasattr(metadata, 'mcp_servers') and metadata.mcp_servers:
590
+ logger.warning(
591
+ "DEPRECATED: mcp_servers in agent schema is ignored. "
592
+ "MCP servers are auto-detected from tools.mcp_server module. "
593
+ "Remove mcp_servers from your agent schema."
594
+ )
595
+
596
596
  if metadata.system_prompt:
597
597
  logger.debug("Using custom system_prompt from json_schema_extra")
598
598
  else:
599
599
  system_prompt = ""
600
600
  metadata = None
601
- mcp_server_configs = []
602
601
  resource_configs = []
603
602
 
604
- # Auto-detect local MCP server if not explicitly configured
605
- # This makes mcp_servers config optional - agents get tools automatically
606
- # But if mcp_servers: [] is explicitly set, respect that (no auto-detection)
607
- if not mcp_server_configs and not mcp_servers_explicitly_set:
608
- import importlib
609
- import os
610
- import sys
611
-
612
- # Ensure current working directory is in sys.path for local imports
613
- cwd = os.getcwd()
614
- if cwd not in sys.path:
615
- sys.path.insert(0, cwd)
616
-
617
- # Try common local MCP server module paths first
618
- auto_detect_modules = [
619
- "tools.mcp_server", # Convention: tools/mcp_server.py
620
- "mcp_server", # Alternative: mcp_server.py in root
621
- ]
622
- for module_path in auto_detect_modules:
623
- try:
624
- mcp_module = importlib.import_module(module_path)
625
- if hasattr(mcp_module, "mcp"):
626
- logger.info(f"Auto-detected local MCP server: {module_path}")
627
- mcp_server_configs = [{"type": "local", "module": module_path, "id": "auto-detected"}]
628
- break
629
- except ImportError:
630
- continue
631
-
632
- # Fall back to REM's default MCP server if no local server found
633
- if not mcp_server_configs:
634
- logger.debug("No local MCP server found, using REM default")
635
- mcp_server_configs = [{"type": "local", "module": "rem.mcp_server", "id": "rem"}]
603
+ # Auto-detect MCP server at application level
604
+ # Convention: tools/mcp_server.py exports `mcp` FastMCP instance
605
+ # Falls back to REM's built-in MCP server if no local server found
606
+ import importlib
607
+ import os
608
+ import sys
609
+
610
+ # Ensure current working directory is in sys.path for local imports
611
+ cwd = os.getcwd()
612
+ if cwd not in sys.path:
613
+ sys.path.insert(0, cwd)
614
+
615
+ mcp_server_configs = []
616
+ auto_detect_modules = [
617
+ "tools.mcp_server", # Convention: tools/mcp_server.py
618
+ "mcp_server", # Alternative: mcp_server.py in root
619
+ ]
620
+ for module_path in auto_detect_modules:
621
+ try:
622
+ mcp_module = importlib.import_module(module_path)
623
+ if hasattr(mcp_module, "mcp"):
624
+ logger.info(f"Auto-detected local MCP server: {module_path}")
625
+ mcp_server_configs = [{"type": "local", "module": module_path, "id": "auto-detected"}]
626
+ break
627
+ except ImportError as e:
628
+ logger.debug(f"MCP server auto-detect: {module_path} not found ({e})")
629
+ continue
630
+ except Exception as e:
631
+ logger.warning(f"MCP server auto-detect: {module_path} failed to load: {e}")
632
+ continue
633
+
634
+ # Fall back to REM's default MCP server if no local server found
635
+ if not mcp_server_configs:
636
+ logger.info("No local MCP server found, using REM default (rem.mcp_server)")
637
+ mcp_server_configs = [{"type": "local", "module": "rem.mcp_server", "id": "rem"}]
636
638
 
637
639
  # Extract temperature and max_iterations from schema metadata (with fallback to settings defaults)
638
640
  if metadata:
@@ -730,7 +732,7 @@ async def create_agent(
730
732
  # the artificial MCP distinction between tools and resources
731
733
  #
732
734
  # Supports both concrete and template URIs:
733
- # - Concrete: "rem://schemas" -> no-param tool
735
+ # - Concrete: "rem://agents" -> no-param tool
734
736
  # - Template: "patient-profile://field/{field_key}" -> tool with field_key param
735
737
  from ..mcp.tool_wrapper import create_resource_tool
736
738
 
@@ -766,6 +768,7 @@ async def create_agent(
766
768
 
767
769
  # Create tools from collected resource URIs
768
770
  # Pass the loaded MCP server so resources can be resolved from it
771
+ logger.info(f"Creating {len(resource_uris)} resource tools with mcp_server={'set' if loaded_mcp_server else 'None'}")
769
772
  for uri, usage in resource_uris:
770
773
  resource_tool = create_resource_tool(uri, usage, mcp_server=loaded_mcp_server)
771
774
  tools.append(resource_tool)
rem/agentic/schema.py CHANGED
@@ -79,7 +79,7 @@ class MCPResourceReference(BaseModel):
79
79
 
80
80
  Example (exact URI):
81
81
  {
82
- "uri": "rem://schemas",
82
+ "uri": "rem://agents",
83
83
  "name": "Agent Schemas",
84
84
  "description": "List all available agent schemas"
85
85
  }
@@ -96,7 +96,7 @@ class MCPResourceReference(BaseModel):
96
96
  default=None,
97
97
  description=(
98
98
  "Exact resource URI or URI with query parameters. "
99
- "Examples: 'rem://schemas', 'rem://resources?category=drug.*'"
99
+ "Examples: 'rem://agents', 'rem://resources?category=drug.*'"
100
100
  )
101
101
  )
102
102
 
@@ -3,6 +3,17 @@ REM tools for agent execution (CLI and API compatible).
3
3
 
4
4
  These tools work in both CLI and API contexts by initializing services on-demand.
5
5
  They wrap the service layer directly, not MCP tools.
6
+
7
+ Core tables (always available):
8
+ - resources: Documents, content chunks, artifacts
9
+ - moments: Temporal narratives extracted from resources (usually user-specific)
10
+ - ontologies: Domain entities with semantic links for further lookups (like a wiki)
11
+
12
+ Other tables (may vary by deployment):
13
+ - users, sessions, messages, files, schemas, feedbacks
14
+
15
+ Note: Not all tables are populated in all systems. Use FUZZY or SEARCH
16
+ to discover what data exists before assuming specific tables have content.
6
17
  """
7
18
 
8
19
  from typing import Any, Literal, cast
rem/api/main.py CHANGED
@@ -322,7 +322,7 @@ def create_app() -> FastAPI:
322
322
 
323
323
  app.add_middleware(
324
324
  AuthMiddleware,
325
- protected_paths=["/api/v1"],
325
+ protected_paths=["/api/v1", "/api/admin"],
326
326
  excluded_paths=["/api/auth", "/api/dev", "/api/v1/mcp/auth", "/api/v1/slack"],
327
327
  # Allow anonymous when auth is disabled, otherwise use setting
328
328
  allow_anonymous=(not settings.auth.enabled) or settings.auth.allow_anonymous,
@@ -349,6 +349,53 @@ def register_agent_resources(mcp: FastMCP):
349
349
  except Exception as e:
350
350
  return f"# Available Agents\n\nError listing agents: {e}"
351
351
 
352
+ @mcp.resource("rem://agents/{agent_name}")
353
+ def get_agent_schema(agent_name: str) -> str:
354
+ """
355
+ Get a specific agent schema by name.
356
+
357
+ Args:
358
+ agent_name: Name of the agent (e.g., "ask_rem", "agent-builder")
359
+
360
+ Returns:
361
+ Full agent schema as YAML string, or error message if not found.
362
+ """
363
+ import importlib.resources
364
+ import yaml
365
+ from pathlib import Path
366
+
367
+ try:
368
+ # Find packaged agent schemas
369
+ agents_ref = importlib.resources.files("rem") / "schemas" / "agents"
370
+ agents_dir = Path(str(agents_ref))
371
+
372
+ if not agents_dir.exists():
373
+ return f"# Agent Not Found\n\nNo agent schemas directory found."
374
+
375
+ # Search for agent file (try multiple extensions)
376
+ for ext in [".yaml", ".yml", ".json"]:
377
+ # Try exact match first
378
+ agent_file = agents_dir / f"{agent_name}{ext}"
379
+ if agent_file.exists():
380
+ with open(agent_file, "r") as f:
381
+ content = f.read()
382
+ return f"# Agent Schema: {agent_name}\n\n```yaml\n{content}\n```"
383
+
384
+ # Try recursive search
385
+ matches = list(agents_dir.rglob(f"{agent_name}{ext}"))
386
+ if matches:
387
+ with open(matches[0], "r") as f:
388
+ content = f.read()
389
+ return f"# Agent Schema: {agent_name}\n\n```yaml\n{content}\n```"
390
+
391
+ # Not found - list available agents
392
+ available = [f.stem for f in agents_dir.rglob("*.yaml")] + \
393
+ [f.stem for f in agents_dir.rglob("*.yml")]
394
+ return f"# Agent Not Found\n\nAgent '{agent_name}' not found.\n\nAvailable agents: {', '.join(sorted(set(available)))}"
395
+
396
+ except Exception as e:
397
+ return f"# Error\n\nError loading agent '{agent_name}': {e}"
398
+
352
399
 
353
400
  def register_file_resources(mcp: FastMCP):
354
401
  """
@@ -501,10 +548,11 @@ async def load_resource(uri: str) -> dict | str:
501
548
  Load an MCP resource by URI.
502
549
 
503
550
  This function is called by the read_resource tool to dispatch to
504
- registered resource handlers.
551
+ registered resource handlers. Supports both regular resources and
552
+ parameterized resource templates (e.g., rem://agents/{agent_name}).
505
553
 
506
554
  Args:
507
- uri: Resource URI (e.g., "rem://schemas", "rem://status")
555
+ uri: Resource URI (e.g., "rem://agents", "rem://agents/ask_rem", "rem://status")
508
556
 
509
557
  Returns:
510
558
  Resource data (dict or string)
@@ -524,18 +572,26 @@ async def load_resource(uri: str) -> dict | str:
524
572
  register_file_resources(mcp)
525
573
  register_status_resources(mcp)
526
574
 
527
- # Get resources using FastMCP's async get_resources() method
575
+ # 1. Try exact match in regular resources
528
576
  resources = await mcp.get_resources()
529
-
530
577
  if uri in resources:
531
578
  resource = resources[uri]
532
- # Call the underlying function
533
579
  result = resource.fn()
534
- # Handle async functions
535
580
  if inspect.iscoroutine(result):
536
581
  result = await result
537
582
  return result if result else {"error": "Resource returned None"}
538
583
 
539
- # If not found, raise error
540
- available = list(resources.keys())
584
+ # 2. Try matching against parameterized resource templates
585
+ templates = await mcp.get_resource_templates()
586
+ for template_uri, template in templates.items():
587
+ params = template.matches(uri)
588
+ if params is not None:
589
+ # Template matched - call function with extracted parameters
590
+ result = template.fn(**params)
591
+ if inspect.iscoroutine(result):
592
+ result = await result
593
+ return result if result else {"error": "Resource returned None"}
594
+
595
+ # 3. Not found - include both resources and templates in error
596
+ available = list(resources.keys()) + list(templates.keys())
541
597
  raise ValueError(f"Resource not found: {uri}. Available resources: {available}")
@@ -1,7 +1,7 @@
1
1
  """
2
2
  MCP server creation and configuration for REM.
3
3
 
4
- Design Pattern
4
+ Design Pattern
5
5
  1. Create FastMCP server with tools and resources
6
6
  2. Register tools using @mcp.tool() decorator
7
7
  3. Register resources using resource registration functions
@@ -20,10 +20,31 @@ FastMCP Features:
20
20
  """
21
21
 
22
22
  import importlib.metadata
23
+ from functools import wraps
23
24
 
24
25
  from fastmcp import FastMCP
26
+ from loguru import logger
25
27
 
26
28
  from ...settings import settings
29
+ from .prompts import register_prompts
30
+ from .resources import (
31
+ register_agent_resources,
32
+ register_file_resources,
33
+ register_schema_resources,
34
+ register_status_resources,
35
+ )
36
+ from .tools import (
37
+ ask_agent,
38
+ ask_rem_agent,
39
+ get_schema,
40
+ ingest_into_rem,
41
+ list_schema,
42
+ read_resource,
43
+ register_metadata,
44
+ save_agent,
45
+ search_rem,
46
+ test_error_handling,
47
+ )
27
48
 
28
49
  # Get package version
29
50
  try:
@@ -174,18 +195,7 @@ def create_mcp_server(is_local: bool = False) -> FastMCP:
174
195
  ),
175
196
  )
176
197
 
177
- # Register REM tools
178
- from .tools import (
179
- ask_rem_agent,
180
- get_schema,
181
- ingest_into_rem,
182
- list_schema,
183
- read_resource,
184
- register_metadata,
185
- save_agent,
186
- search_rem,
187
- )
188
-
198
+ # Register core REM tools
189
199
  mcp.tool()(search_rem)
190
200
  mcp.tool()(ask_rem_agent)
191
201
  mcp.tool()(read_resource)
@@ -194,10 +204,16 @@ def create_mcp_server(is_local: bool = False) -> FastMCP:
194
204
  mcp.tool()(get_schema)
195
205
  mcp.tool()(save_agent)
196
206
 
207
+ # Register multi-agent tools
208
+ mcp.tool()(ask_agent)
209
+
210
+ # Register test tool only in development environment (not staging/production)
211
+ if settings.environment not in ("staging", "production"):
212
+ mcp.tool()(test_error_handling)
213
+ logger.debug("Registered test_error_handling tool (dev environment only)")
214
+
197
215
  # File ingestion tool (with local path support for local servers)
198
216
  # Wrap to inject is_local parameter
199
- from functools import wraps
200
-
201
217
  @wraps(ingest_into_rem)
202
218
  async def ingest_into_rem_wrapper(
203
219
  file_uri: str,
@@ -216,18 +232,9 @@ def create_mcp_server(is_local: bool = False) -> FastMCP:
216
232
  mcp.tool()(ingest_into_rem_wrapper)
217
233
 
218
234
  # Register prompts
219
- from .prompts import register_prompts
220
-
221
235
  register_prompts(mcp)
222
236
 
223
237
  # Register schema resources
224
- from .resources import (
225
- register_agent_resources,
226
- register_file_resources,
227
- register_schema_resources,
228
- register_status_resources,
229
- )
230
-
231
238
  register_schema_resources(mcp)
232
239
  register_agent_resources(mcp)
233
240
  register_file_resources(mcp)