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
@@ -175,6 +175,23 @@ class AgentRuntime:
175
175
  return self.agent.iter(*args, **kwargs)
176
176
 
177
177
 
178
+ def _get_builtin_tools() -> list:
179
+ """
180
+ Get built-in tools that are always available to agents.
181
+
182
+ Currently returns empty list - all tools come from MCP servers.
183
+ The register_metadata tool is available via the REM MCP server and
184
+ agents can opt-in by configuring mcp_servers in their schema.
185
+
186
+ Returns:
187
+ List of Pydantic AI tool functions (currently empty)
188
+ """
189
+ # NOTE: register_metadata is now an MCP tool, not a built-in.
190
+ # Agents that want it should configure mcp_servers to load from rem.mcp_server.
191
+ # This allows agents to choose which tools they need.
192
+ return []
193
+
194
+
178
195
  def _create_model_from_schema(agent_schema: dict[str, Any]) -> type[BaseModel]:
179
196
  """
180
197
  Create Pydantic model dynamically from JSON Schema.
@@ -530,18 +547,74 @@ async def create_agent(
530
547
  default_model = context.default_model if context else settings.llm.default_model
531
548
  model = get_valid_model_or_default(model_override, default_model)
532
549
 
533
- # Extract schema fields
534
- system_prompt = agent_schema.get("description", "") if agent_schema else ""
535
- metadata = agent_schema.get("json_schema_extra", {}) if agent_schema else {}
536
- mcp_server_configs = metadata.get("mcp_servers", [])
537
- resource_configs = metadata.get("resources", [])
550
+ # Extract schema fields using typed helpers
551
+ from ..schema import get_system_prompt, get_metadata
552
+
553
+ if agent_schema:
554
+ system_prompt = get_system_prompt(agent_schema)
555
+ metadata = get_metadata(agent_schema)
556
+ mcp_server_configs = [s.model_dump() for s in metadata.mcp_servers] if hasattr(metadata, 'mcp_servers') and metadata.mcp_servers else []
557
+ resource_configs = metadata.resources if hasattr(metadata, 'resources') else []
558
+
559
+ if metadata.system_prompt:
560
+ logger.debug("Using custom system_prompt from json_schema_extra")
561
+ else:
562
+ system_prompt = ""
563
+ metadata = None
564
+ mcp_server_configs = []
565
+ resource_configs = []
566
+
567
+ # Auto-detect local MCP server if not explicitly configured
568
+ # This makes mcp_servers config optional - agents get tools automatically
569
+ if not mcp_server_configs:
570
+ import importlib
571
+ import os
572
+ import sys
573
+
574
+ # Ensure current working directory is in sys.path for local imports
575
+ cwd = os.getcwd()
576
+ if cwd not in sys.path:
577
+ sys.path.insert(0, cwd)
578
+
579
+ # Try common local MCP server module paths first
580
+ auto_detect_modules = [
581
+ "tools.mcp_server", # Convention: tools/mcp_server.py
582
+ "mcp_server", # Alternative: mcp_server.py in root
583
+ ]
584
+ for module_path in auto_detect_modules:
585
+ try:
586
+ mcp_module = importlib.import_module(module_path)
587
+ if hasattr(mcp_module, "mcp"):
588
+ logger.info(f"Auto-detected local MCP server: {module_path}")
589
+ mcp_server_configs = [{"type": "local", "module": module_path, "id": "auto-detected"}]
590
+ break
591
+ except ImportError:
592
+ continue
593
+
594
+ # Fall back to REM's default MCP server if no local server found
595
+ if not mcp_server_configs:
596
+ logger.debug("No local MCP server found, using REM default")
597
+ mcp_server_configs = [{"type": "local", "module": "rem.mcp_server", "id": "rem"}]
538
598
 
539
599
  # Extract temperature and max_iterations from schema metadata (with fallback to settings defaults)
540
- temperature = metadata.get("override_temperature", settings.llm.default_temperature)
541
- max_iterations = metadata.get("override_max_iterations", settings.llm.default_max_iterations)
600
+ if metadata:
601
+ temperature = metadata.override_temperature if metadata.override_temperature is not None else settings.llm.default_temperature
602
+ max_iterations = metadata.override_max_iterations if metadata.override_max_iterations is not None else settings.llm.default_max_iterations
603
+ use_structured_output = metadata.structured_output
604
+ else:
605
+ temperature = settings.llm.default_temperature
606
+ max_iterations = settings.llm.default_max_iterations
607
+ use_structured_output = True
608
+
609
+ # Build list of tools - start with built-in tools
610
+ tools = _get_builtin_tools()
611
+
612
+ # Get agent name from metadata for logging
613
+ agent_name = metadata.name if metadata and hasattr(metadata, 'name') else "unknown"
542
614
 
543
615
  logger.info(
544
- f"Creating agent: model={model}, mcp_servers={len(mcp_server_configs)}, resources={len(resource_configs)}"
616
+ f"Creating agent '{agent_name}': model={model}, mcp_servers={len(mcp_server_configs)}, "
617
+ f"resources={len(resource_configs)}, builtin_tools={len(tools)}"
545
618
  )
546
619
 
547
620
  # Set agent resource attributes for OTEL (before creating agent)
@@ -550,50 +623,117 @@ async def create_agent(
550
623
 
551
624
  set_agent_resource_attributes(agent_schema=agent_schema)
552
625
 
553
- # Build list of tools from MCP server (in-process, no subprocess)
554
- tools = []
555
- if mcp_server_configs:
556
- for server_config in mcp_server_configs:
557
- server_type = server_config.get("type")
558
- server_id = server_config.get("id", "mcp-server")
559
-
560
- if server_type == "local":
561
- # Import MCP server directly (in-process)
562
- module_path = server_config.get("module", "rem.mcp_server")
563
-
564
- try:
565
- # Dynamic import of MCP server module
566
- import importlib
567
- mcp_module = importlib.import_module(module_path)
568
- mcp_server = mcp_module.mcp
569
-
570
- # Extract tools from MCP server (get_tools is async)
571
- from ..mcp.tool_wrapper import create_mcp_tool_wrapper
572
-
573
- # Await async get_tools() call
574
- mcp_tools_dict = await mcp_server.get_tools()
626
+ # Extract schema metadata for search_rem tool description suffix
627
+ # This allows entity schemas to add context-specific notes to the search_rem tool
628
+ search_rem_suffix = None
629
+ if metadata:
630
+ # Check for default_search_table in metadata (set by entity schemas)
631
+ extra = agent_schema.get("json_schema_extra", {}) if agent_schema else {}
632
+ default_table = extra.get("default_search_table")
633
+ has_embeddings = extra.get("has_embeddings", False)
634
+
635
+ if default_table:
636
+ # Build description suffix for search_rem
637
+ search_rem_suffix = f"\n\nFor this schema, use `search_rem` to query `{default_table}`. "
638
+ if has_embeddings:
639
+ search_rem_suffix += f"SEARCH works well on {default_table} (has embeddings). "
640
+ search_rem_suffix += f"Example: `SEARCH \"your query\" FROM {default_table} LIMIT 10`"
641
+
642
+ # Add tools from MCP server (in-process, no subprocess)
643
+ # Track loaded MCP servers for resource resolution
644
+ loaded_mcp_server = None
645
+
646
+ for server_config in mcp_server_configs:
647
+ server_type = server_config.get("type")
648
+ server_id = server_config.get("id", "mcp-server")
649
+
650
+ if server_type == "local":
651
+ # Import MCP server directly (in-process)
652
+ module_path = server_config.get("module", "rem.mcp_server")
653
+
654
+ try:
655
+ # Dynamic import of MCP server module
656
+ import importlib
657
+ mcp_module = importlib.import_module(module_path)
658
+ mcp_server = mcp_module.mcp
659
+
660
+ # Store the loaded server for resource resolution
661
+ loaded_mcp_server = mcp_server
662
+
663
+ # Extract tools from MCP server (get_tools is async)
664
+ from ..mcp.tool_wrapper import create_mcp_tool_wrapper
665
+
666
+ # Await async get_tools() call
667
+ mcp_tools_dict = await mcp_server.get_tools()
668
+
669
+ for tool_name, tool_func in mcp_tools_dict.items():
670
+ # Add description suffix to search_rem tool if schema specifies a default table
671
+ tool_suffix = search_rem_suffix if tool_name == "search_rem" else None
672
+
673
+ wrapped_tool = create_mcp_tool_wrapper(
674
+ tool_name,
675
+ tool_func,
676
+ user_id=context.user_id if context else None,
677
+ description_suffix=tool_suffix,
678
+ )
679
+ tools.append(wrapped_tool)
680
+ logger.debug(f"Loaded MCP tool: {tool_name}" + (" (with schema suffix)" if tool_suffix else ""))
681
+
682
+ logger.info(f"Loaded {len(mcp_tools_dict)} tools from MCP server: {server_id} (in-process)")
683
+
684
+ except Exception as e:
685
+ logger.error(f"Failed to load MCP server {server_id}: {e}", exc_info=True)
686
+ else:
687
+ logger.warning(f"Unsupported MCP server type: {server_type}")
575
688
 
576
- for tool_name, tool_func in mcp_tools_dict.items():
577
- wrapped_tool = create_mcp_tool_wrapper(tool_name, tool_func, user_id=context.user_id if context else None)
578
- tools.append(wrapped_tool)
579
- logger.debug(f"Loaded MCP tool: {tool_name}")
689
+ # Convert resources to tools (MCP convenience syntax)
690
+ # Resources declared in agent YAML become callable tools - eliminates
691
+ # the artificial MCP distinction between tools and resources
692
+ #
693
+ # Supports both concrete and template URIs:
694
+ # - Concrete: "rem://schemas" -> no-param tool
695
+ # - Template: "patient-profile://field/{field_key}" -> tool with field_key param
696
+ from ..mcp.tool_wrapper import create_resource_tool
580
697
 
581
- logger.info(f"Loaded {len(mcp_tools_dict)} tools from MCP server: {server_id} (in-process)")
698
+ # Collect all resource URIs from both resources section AND tools section
699
+ resource_uris = []
582
700
 
583
- except Exception as e:
584
- logger.error(f"Failed to load MCP server {server_id}: {e}", exc_info=True)
701
+ # From resources section (legacy format)
702
+ if resource_configs:
703
+ for resource_config in resource_configs:
704
+ if hasattr(resource_config, 'uri'):
705
+ uri = resource_config.uri
706
+ usage = resource_config.description or ""
585
707
  else:
586
- logger.warning(f"Unsupported MCP server type: {server_type}")
708
+ uri = resource_config.get("uri", "")
709
+ usage = resource_config.get("description", "")
710
+ if uri:
711
+ resource_uris.append((uri, usage))
712
+
713
+ # From tools section - detect URIs (anything with ://)
714
+ # This allows unified syntax: resources as tools
715
+ tool_configs = metadata.tools if metadata and hasattr(metadata, 'tools') else []
716
+ for tool_config in tool_configs:
717
+ if hasattr(tool_config, 'name'):
718
+ tool_name = tool_config.name
719
+ tool_desc = tool_config.description or ""
720
+ else:
721
+ tool_name = tool_config.get("name", "")
722
+ tool_desc = tool_config.get("description", "")
587
723
 
588
- if resource_configs:
589
- # TODO: Convert resources to tools (MCP convenience syntax)
590
- pass
724
+ # Auto-detect resource URIs (anything with :// scheme)
725
+ if "://" in tool_name:
726
+ resource_uris.append((tool_name, tool_desc))
591
727
 
592
- # Check if structured output is disabled for this schema
593
- # When structured_output: false, properties become part of prompt instead of output_type
594
- use_structured_output = metadata.get("structured_output", True)
728
+ # Create tools from collected resource URIs
729
+ # Pass the loaded MCP server so resources can be resolved from it
730
+ for uri, usage in resource_uris:
731
+ resource_tool = create_resource_tool(uri, usage, mcp_server=loaded_mcp_server)
732
+ tools.append(resource_tool)
733
+ logger.debug(f"Loaded resource as tool: {uri}")
595
734
 
596
735
  # Create dynamic result_type from schema if not provided
736
+ # Note: use_structured_output is set earlier from metadata.structured_output
597
737
  if result_type is None and agent_schema and "properties" in agent_schema:
598
738
  if use_structured_output:
599
739
  # Pre-process schema for Qwen compatibility (strips min/max, sets additionalProperties=False)
@@ -615,21 +755,30 @@ async def create_agent(
615
755
  wrapped_result_type = _create_schema_wrapper(
616
756
  result_type, strip_description=strip_model_description
617
757
  )
758
+ # Use InstrumentationSettings with version=3 to include agent name in span names
759
+ from pydantic_ai.models.instrumented import InstrumentationSettings
760
+ instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
761
+
618
762
  agent = Agent(
619
763
  model=model,
764
+ name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
620
765
  system_prompt=system_prompt,
621
766
  output_type=wrapped_result_type,
622
767
  tools=tools,
623
- instrument=settings.otel.enabled, # Conditional OTEL instrumentation
768
+ instrument=instrumentation,
624
769
  model_settings={"temperature": temperature},
625
770
  retries=settings.llm.max_retries,
626
771
  )
627
772
  else:
773
+ from pydantic_ai.models.instrumented import InstrumentationSettings
774
+ instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
775
+
628
776
  agent = Agent(
629
777
  model=model,
778
+ name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
630
779
  system_prompt=system_prompt,
631
780
  tools=tools,
632
- instrument=settings.otel.enabled,
781
+ instrument=instrumentation,
633
782
  model_settings={"temperature": temperature},
634
783
  retries=settings.llm.max_retries,
635
784
  )