remdb 0.2.6__py3-none-any.whl → 0.3.118__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 (104) hide show
  1. rem/__init__.py +129 -2
  2. rem/agentic/README.md +76 -0
  3. rem/agentic/__init__.py +15 -0
  4. rem/agentic/agents/__init__.py +16 -2
  5. rem/agentic/agents/sse_simulator.py +500 -0
  6. rem/agentic/context.py +28 -22
  7. rem/agentic/llm_provider_models.py +301 -0
  8. rem/agentic/mcp/tool_wrapper.py +29 -3
  9. rem/agentic/otel/setup.py +92 -4
  10. rem/agentic/providers/phoenix.py +32 -43
  11. rem/agentic/providers/pydantic_ai.py +168 -24
  12. rem/agentic/schema.py +358 -21
  13. rem/agentic/tools/rem_tools.py +3 -3
  14. rem/api/README.md +238 -1
  15. rem/api/deps.py +255 -0
  16. rem/api/main.py +154 -37
  17. rem/api/mcp_router/resources.py +1 -1
  18. rem/api/mcp_router/server.py +26 -5
  19. rem/api/mcp_router/tools.py +454 -7
  20. rem/api/middleware/tracking.py +172 -0
  21. rem/api/routers/admin.py +494 -0
  22. rem/api/routers/auth.py +124 -0
  23. rem/api/routers/chat/completions.py +152 -16
  24. rem/api/routers/chat/models.py +7 -3
  25. rem/api/routers/chat/sse_events.py +526 -0
  26. rem/api/routers/chat/streaming.py +608 -45
  27. rem/api/routers/dev.py +81 -0
  28. rem/api/routers/feedback.py +148 -0
  29. rem/api/routers/messages.py +473 -0
  30. rem/api/routers/models.py +78 -0
  31. rem/api/routers/query.py +360 -0
  32. rem/api/routers/shared_sessions.py +406 -0
  33. rem/auth/middleware.py +126 -27
  34. rem/cli/commands/README.md +237 -64
  35. rem/cli/commands/ask.py +15 -11
  36. rem/cli/commands/cluster.py +1300 -0
  37. rem/cli/commands/configure.py +170 -97
  38. rem/cli/commands/db.py +396 -139
  39. rem/cli/commands/experiments.py +278 -96
  40. rem/cli/commands/process.py +22 -15
  41. rem/cli/commands/scaffold.py +47 -0
  42. rem/cli/commands/schema.py +97 -50
  43. rem/cli/main.py +37 -6
  44. rem/config.py +2 -2
  45. rem/models/core/core_model.py +7 -1
  46. rem/models/core/rem_query.py +5 -2
  47. rem/models/entities/__init__.py +21 -0
  48. rem/models/entities/domain_resource.py +38 -0
  49. rem/models/entities/feedback.py +123 -0
  50. rem/models/entities/message.py +30 -1
  51. rem/models/entities/session.py +83 -0
  52. rem/models/entities/shared_session.py +180 -0
  53. rem/models/entities/user.py +10 -3
  54. rem/registry.py +373 -0
  55. rem/schemas/agents/rem.yaml +7 -3
  56. rem/services/content/providers.py +94 -140
  57. rem/services/content/service.py +115 -24
  58. rem/services/dreaming/affinity_service.py +2 -16
  59. rem/services/dreaming/moment_service.py +2 -15
  60. rem/services/embeddings/api.py +24 -17
  61. rem/services/embeddings/worker.py +16 -16
  62. rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
  63. rem/services/phoenix/client.py +252 -19
  64. rem/services/postgres/README.md +159 -15
  65. rem/services/postgres/__init__.py +2 -1
  66. rem/services/postgres/diff_service.py +531 -0
  67. rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
  68. rem/services/postgres/repository.py +132 -0
  69. rem/services/postgres/schema_generator.py +291 -9
  70. rem/services/postgres/service.py +6 -6
  71. rem/services/rate_limit.py +113 -0
  72. rem/services/rem/README.md +14 -0
  73. rem/services/rem/parser.py +44 -9
  74. rem/services/rem/service.py +36 -2
  75. rem/services/session/compression.py +17 -1
  76. rem/services/session/reload.py +1 -1
  77. rem/services/user_service.py +98 -0
  78. rem/settings.py +169 -22
  79. rem/sql/background_indexes.sql +21 -16
  80. rem/sql/migrations/001_install.sql +387 -54
  81. rem/sql/migrations/002_install_models.sql +2320 -393
  82. rem/sql/migrations/003_optional_extensions.sql +326 -0
  83. rem/sql/migrations/004_cache_system.sql +548 -0
  84. rem/utils/__init__.py +18 -0
  85. rem/utils/constants.py +97 -0
  86. rem/utils/date_utils.py +228 -0
  87. rem/utils/embeddings.py +17 -4
  88. rem/utils/files.py +167 -0
  89. rem/utils/mime_types.py +158 -0
  90. rem/utils/model_helpers.py +156 -1
  91. rem/utils/schema_loader.py +284 -21
  92. rem/utils/sql_paths.py +146 -0
  93. rem/utils/sql_types.py +3 -1
  94. rem/utils/vision.py +9 -14
  95. rem/workers/README.md +14 -14
  96. rem/workers/__init__.py +2 -1
  97. rem/workers/db_maintainer.py +74 -0
  98. rem/workers/unlogged_maintainer.py +463 -0
  99. {remdb-0.2.6.dist-info → remdb-0.3.118.dist-info}/METADATA +598 -171
  100. {remdb-0.2.6.dist-info → remdb-0.3.118.dist-info}/RECORD +102 -73
  101. {remdb-0.2.6.dist-info → remdb-0.3.118.dist-info}/WHEEL +1 -1
  102. rem/sql/002_install_models.sql +0 -1068
  103. rem/sql/install_models.sql +0 -1038
  104. {remdb-0.2.6.dist-info → remdb-0.3.118.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.
@@ -303,6 +320,68 @@ def _prepare_schema_for_qwen(schema: dict[str, Any]) -> dict[str, Any]:
303
320
  return schema_copy
304
321
 
305
322
 
323
+ def _convert_properties_to_prompt(properties: dict[str, Any]) -> str:
324
+ """
325
+ Convert schema properties to prompt guidance text.
326
+
327
+ When structured_output is disabled, this converts the properties
328
+ definition into natural language guidance that informs the agent
329
+ about the expected response structure without forcing JSON output.
330
+
331
+ Args:
332
+ properties: JSON Schema properties dict
333
+
334
+ Returns:
335
+ Prompt text describing the expected response elements
336
+
337
+ Example:
338
+ properties = {
339
+ "answer": {"type": "string", "description": "The answer"},
340
+ "confidence": {"type": "number", "description": "Confidence 0-1"}
341
+ }
342
+ # Returns:
343
+ # "## Response Structure\n\nYour response should include:\n- **answer**: The answer\n..."
344
+ """
345
+ if not properties:
346
+ return ""
347
+
348
+ lines = ["## Response Guidelines", "", "Your response should address the following elements:"]
349
+
350
+ for field_name, field_def in properties.items():
351
+ field_type = field_def.get("type", "any")
352
+ description = field_def.get("description", "")
353
+
354
+ # Format based on type
355
+ if field_type == "array":
356
+ type_hint = "list"
357
+ elif field_type == "number":
358
+ type_hint = "number"
359
+ # Include min/max if specified
360
+ if "minimum" in field_def or "maximum" in field_def:
361
+ min_val = field_def.get("minimum", "")
362
+ max_val = field_def.get("maximum", "")
363
+ if min_val != "" and max_val != "":
364
+ type_hint = f"number ({min_val}-{max_val})"
365
+ elif field_type == "boolean":
366
+ type_hint = "yes/no"
367
+ else:
368
+ type_hint = field_type
369
+
370
+ # Build field description
371
+ field_line = f"- **{field_name}**"
372
+ if type_hint and type_hint != "string":
373
+ field_line += f" ({type_hint})"
374
+ if description:
375
+ field_line += f": {description}"
376
+
377
+ lines.append(field_line)
378
+
379
+ lines.append("")
380
+ lines.append("Respond naturally in prose, addressing these elements where relevant.")
381
+
382
+ return "\n".join(lines)
383
+
384
+
306
385
  def _create_schema_wrapper(
307
386
  result_type: type[BaseModel], strip_description: bool = True
308
387
  ) -> type[BaseModel]:
@@ -462,23 +541,48 @@ async def create_agent(
462
541
  # agent_schema = load_agent_schema(context.agent_schema_uri)
463
542
  pass
464
543
 
465
- # Determine model: override > context.default_model > settings
466
- model = (
467
- model_override or (context.default_model if context else settings.llm.default_model)
468
- )
544
+ # Determine model: validate override against allowed list, fallback to context or settings
545
+ from rem.agentic.llm_provider_models import get_valid_model_or_default
546
+
547
+ default_model = context.default_model if context else settings.llm.default_model
548
+ model = get_valid_model_or_default(model_override, default_model)
549
+
550
+ # Extract schema fields using typed helpers
551
+ from ..schema import get_system_prompt, get_metadata
469
552
 
470
- # Extract schema fields
471
- system_prompt = agent_schema.get("description", "") if agent_schema else ""
472
- metadata = agent_schema.get("json_schema_extra", {}) if agent_schema else {}
473
- mcp_server_configs = metadata.get("mcp_servers", [])
474
- resource_configs = metadata.get("resources", [])
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') 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 = []
475
566
 
476
567
  # Extract temperature and max_iterations from schema metadata (with fallback to settings defaults)
477
- temperature = metadata.get("override_temperature", settings.llm.default_temperature)
478
- max_iterations = metadata.get("override_max_iterations", settings.llm.default_max_iterations)
568
+ if metadata:
569
+ temperature = metadata.override_temperature if metadata.override_temperature is not None else settings.llm.default_temperature
570
+ max_iterations = metadata.override_max_iterations if metadata.override_max_iterations is not None else settings.llm.default_max_iterations
571
+ use_structured_output = metadata.structured_output
572
+ else:
573
+ temperature = settings.llm.default_temperature
574
+ max_iterations = settings.llm.default_max_iterations
575
+ use_structured_output = True
576
+
577
+ # Build list of tools - start with built-in tools
578
+ tools = _get_builtin_tools()
579
+
580
+ # Get agent name from metadata for logging
581
+ agent_name = metadata.name if metadata and hasattr(metadata, 'name') else "unknown"
479
582
 
480
583
  logger.info(
481
- f"Creating agent: model={model}, mcp_servers={len(mcp_server_configs)}, resources={len(resource_configs)}"
584
+ f"Creating agent '{agent_name}': model={model}, mcp_servers={len(mcp_server_configs)}, "
585
+ f"resources={len(resource_configs)}, builtin_tools={len(tools)}"
482
586
  )
483
587
 
484
588
  # Set agent resource attributes for OTEL (before creating agent)
@@ -487,8 +591,23 @@ async def create_agent(
487
591
 
488
592
  set_agent_resource_attributes(agent_schema=agent_schema)
489
593
 
490
- # Build list of tools from MCP server (in-process, no subprocess)
491
- tools = []
594
+ # Extract schema metadata for search_rem tool description suffix
595
+ # This allows entity schemas to add context-specific notes to the search_rem tool
596
+ search_rem_suffix = None
597
+ if metadata:
598
+ # Check for default_search_table in metadata (set by entity schemas)
599
+ extra = agent_schema.get("json_schema_extra", {}) if agent_schema else {}
600
+ default_table = extra.get("default_search_table")
601
+ has_embeddings = extra.get("has_embeddings", False)
602
+
603
+ if default_table:
604
+ # Build description suffix for search_rem
605
+ search_rem_suffix = f"\n\nFor this schema, use `search_rem` to query `{default_table}`. "
606
+ if has_embeddings:
607
+ search_rem_suffix += f"SEARCH works well on {default_table} (has embeddings). "
608
+ search_rem_suffix += f"Example: `SEARCH \"your query\" FROM {default_table} LIMIT 10`"
609
+
610
+ # Add tools from MCP server (in-process, no subprocess)
492
611
  if mcp_server_configs:
493
612
  for server_config in mcp_server_configs:
494
613
  server_type = server_config.get("type")
@@ -511,9 +630,17 @@ async def create_agent(
511
630
  mcp_tools_dict = await mcp_server.get_tools()
512
631
 
513
632
  for tool_name, tool_func in mcp_tools_dict.items():
514
- wrapped_tool = create_mcp_tool_wrapper(tool_name, tool_func, user_id=context.user_id if context else None)
633
+ # Add description suffix to search_rem tool if schema specifies a default table
634
+ tool_suffix = search_rem_suffix if tool_name == "search_rem" else None
635
+
636
+ wrapped_tool = create_mcp_tool_wrapper(
637
+ tool_name,
638
+ tool_func,
639
+ user_id=context.user_id if context else None,
640
+ description_suffix=tool_suffix,
641
+ )
515
642
  tools.append(wrapped_tool)
516
- logger.debug(f"Loaded MCP tool: {tool_name}")
643
+ logger.debug(f"Loaded MCP tool: {tool_name}" + (" (with schema suffix)" if tool_suffix else ""))
517
644
 
518
645
  logger.info(f"Loaded {len(mcp_tools_dict)} tools from MCP server: {server_id} (in-process)")
519
646
 
@@ -527,13 +654,21 @@ async def create_agent(
527
654
  pass
528
655
 
529
656
  # Create dynamic result_type from schema if not provided
657
+ # Note: use_structured_output is set earlier from metadata.structured_output
530
658
  if result_type is None and agent_schema and "properties" in agent_schema:
531
- # Pre-process schema for Qwen compatibility (strips min/max, sets additionalProperties=False)
532
- # This ensures the generated Pydantic model doesn't have incompatible constraints
533
- sanitized_schema = _prepare_schema_for_qwen(agent_schema)
534
- result_type = _create_model_from_schema(sanitized_schema)
535
- logger.debug(f"Created dynamic Pydantic model: {result_type.__name__}")
536
- logger.debug(f"Created dynamic Pydantic model: {result_type.__name__}")
659
+ if use_structured_output:
660
+ # Pre-process schema for Qwen compatibility (strips min/max, sets additionalProperties=False)
661
+ # This ensures the generated Pydantic model doesn't have incompatible constraints
662
+ sanitized_schema = _prepare_schema_for_qwen(agent_schema)
663
+ result_type = _create_model_from_schema(sanitized_schema)
664
+ logger.debug(f"Created dynamic Pydantic model: {result_type.__name__}")
665
+ else:
666
+ # Convert properties to prompt guidance instead of structured output
667
+ # This informs the agent about expected response structure without forcing it
668
+ properties_prompt = _convert_properties_to_prompt(agent_schema.get("properties", {}))
669
+ if properties_prompt:
670
+ system_prompt = system_prompt + "\n\n" + properties_prompt
671
+ logger.debug("Structured output disabled - properties converted to prompt guidance")
537
672
 
538
673
  # Create agent with optional output_type for structured output and tools
539
674
  if result_type:
@@ -541,21 +676,30 @@ async def create_agent(
541
676
  wrapped_result_type = _create_schema_wrapper(
542
677
  result_type, strip_description=strip_model_description
543
678
  )
679
+ # Use InstrumentationSettings with version=3 to include agent name in span names
680
+ from pydantic_ai.models.instrumented import InstrumentationSettings
681
+ instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
682
+
544
683
  agent = Agent(
545
684
  model=model,
685
+ name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
546
686
  system_prompt=system_prompt,
547
687
  output_type=wrapped_result_type,
548
688
  tools=tools,
549
- instrument=settings.otel.enabled, # Conditional OTEL instrumentation
689
+ instrument=instrumentation,
550
690
  model_settings={"temperature": temperature},
551
691
  retries=settings.llm.max_retries,
552
692
  )
553
693
  else:
694
+ from pydantic_ai.models.instrumented import InstrumentationSettings
695
+ instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
696
+
554
697
  agent = Agent(
555
698
  model=model,
699
+ name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
556
700
  system_prompt=system_prompt,
557
701
  tools=tools,
558
- instrument=settings.otel.enabled,
702
+ instrument=instrumentation,
559
703
  model_settings={"temperature": temperature},
560
704
  retries=settings.llm.max_retries,
561
705
  )