remdb 0.3.14__py3-none-any.whl → 0.3.133__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 +76 -0
- rem/agentic/__init__.py +15 -0
- rem/agentic/agents/__init__.py +16 -2
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +51 -27
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/tool_wrapper.py +112 -17
- rem/agentic/otel/setup.py +93 -4
- rem/agentic/providers/phoenix.py +302 -109
- rem/agentic/providers/pydantic_ai.py +215 -26
- rem/agentic/schema.py +361 -21
- rem/agentic/tools/rem_tools.py +3 -3
- rem/api/README.md +215 -1
- rem/api/deps.py +255 -0
- rem/api/main.py +132 -40
- rem/api/mcp_router/resources.py +1 -1
- rem/api/mcp_router/server.py +26 -5
- rem/api/mcp_router/tools.py +465 -7
- rem/api/routers/admin.py +494 -0
- rem/api/routers/auth.py +70 -0
- rem/api/routers/chat/completions.py +402 -20
- rem/api/routers/chat/models.py +88 -10
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +542 -0
- rem/api/routers/chat/streaming.py +642 -45
- rem/api/routers/dev.py +81 -0
- rem/api/routers/feedback.py +268 -0
- rem/api/routers/messages.py +473 -0
- rem/api/routers/models.py +78 -0
- rem/api/routers/query.py +360 -0
- rem/api/routers/shared_sessions.py +406 -0
- rem/auth/middleware.py +126 -27
- rem/cli/commands/README.md +237 -64
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +1 -3
- rem/cli/commands/db.py +386 -143
- rem/cli/commands/experiments.py +418 -27
- rem/cli/commands/process.py +14 -8
- rem/cli/commands/schema.py +97 -50
- rem/cli/main.py +27 -6
- rem/config.py +10 -3
- rem/models/core/core_model.py +7 -1
- rem/models/core/experiment.py +54 -0
- rem/models/core/rem_query.py +5 -2
- rem/models/entities/__init__.py +21 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/message.py +30 -1
- rem/models/entities/session.py +83 -0
- rem/models/entities/shared_session.py +180 -0
- rem/registry.py +10 -4
- rem/schemas/agents/rem.yaml +7 -3
- rem/services/content/service.py +92 -20
- rem/services/embeddings/api.py +4 -4
- rem/services/embeddings/worker.py +16 -16
- rem/services/phoenix/client.py +154 -14
- rem/services/postgres/README.md +159 -15
- rem/services/postgres/__init__.py +2 -1
- rem/services/postgres/diff_service.py +531 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
- rem/services/postgres/repository.py +132 -0
- rem/services/postgres/schema_generator.py +205 -4
- rem/services/postgres/service.py +6 -6
- rem/services/rem/parser.py +44 -9
- rem/services/rem/service.py +36 -2
- rem/services/session/compression.py +24 -1
- rem/services/session/reload.py +1 -1
- rem/settings.py +324 -23
- rem/sql/background_indexes.sql +21 -16
- rem/sql/migrations/001_install.sql +387 -54
- rem/sql/migrations/002_install_models.sql +2320 -393
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +548 -0
- rem/utils/__init__.py +18 -0
- rem/utils/date_utils.py +2 -2
- rem/utils/model_helpers.py +156 -1
- rem/utils/schema_loader.py +220 -22
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +3 -1
- rem/workers/__init__.py +3 -1
- rem/workers/db_listener.py +579 -0
- rem/workers/unlogged_maintainer.py +463 -0
- {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/METADATA +335 -226
- {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/RECORD +86 -66
- {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/WHEEL +1 -1
- rem/sql/002_install_models.sql +0 -1068
- rem/sql/install_models.sql +0 -1051
- rem/sql/migrations/003_seed_default_user.sql +0 -48
- {remdb-0.3.14.dist-info → remdb-0.3.133.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,52 @@ async def create_agent(
|
|
|
462
541
|
# agent_schema = load_agent_schema(context.agent_schema_uri)
|
|
463
542
|
pass
|
|
464
543
|
|
|
465
|
-
# Determine model: override
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
+
# Default to rem.mcp_server if no MCP servers configured
|
|
568
|
+
if not mcp_server_configs:
|
|
569
|
+
mcp_server_configs = [{"type": "local", "module": "rem.mcp_server", "id": "rem"}]
|
|
475
570
|
|
|
476
571
|
# Extract temperature and max_iterations from schema metadata (with fallback to settings defaults)
|
|
477
|
-
|
|
478
|
-
|
|
572
|
+
if metadata:
|
|
573
|
+
temperature = metadata.override_temperature if metadata.override_temperature is not None else settings.llm.default_temperature
|
|
574
|
+
max_iterations = metadata.override_max_iterations if metadata.override_max_iterations is not None else settings.llm.default_max_iterations
|
|
575
|
+
use_structured_output = metadata.structured_output
|
|
576
|
+
else:
|
|
577
|
+
temperature = settings.llm.default_temperature
|
|
578
|
+
max_iterations = settings.llm.default_max_iterations
|
|
579
|
+
use_structured_output = True
|
|
580
|
+
|
|
581
|
+
# Build list of tools - start with built-in tools
|
|
582
|
+
tools = _get_builtin_tools()
|
|
583
|
+
|
|
584
|
+
# Get agent name from metadata for logging
|
|
585
|
+
agent_name = metadata.name if metadata and hasattr(metadata, 'name') else "unknown"
|
|
479
586
|
|
|
480
587
|
logger.info(
|
|
481
|
-
f"Creating agent: model={model}, mcp_servers={len(mcp_server_configs)},
|
|
588
|
+
f"Creating agent '{agent_name}': model={model}, mcp_servers={len(mcp_server_configs)}, "
|
|
589
|
+
f"resources={len(resource_configs)}, builtin_tools={len(tools)}"
|
|
482
590
|
)
|
|
483
591
|
|
|
484
592
|
# Set agent resource attributes for OTEL (before creating agent)
|
|
@@ -487,8 +595,23 @@ async def create_agent(
|
|
|
487
595
|
|
|
488
596
|
set_agent_resource_attributes(agent_schema=agent_schema)
|
|
489
597
|
|
|
490
|
-
#
|
|
491
|
-
|
|
598
|
+
# Extract schema metadata for search_rem tool description suffix
|
|
599
|
+
# This allows entity schemas to add context-specific notes to the search_rem tool
|
|
600
|
+
search_rem_suffix = None
|
|
601
|
+
if metadata:
|
|
602
|
+
# Check for default_search_table in metadata (set by entity schemas)
|
|
603
|
+
extra = agent_schema.get("json_schema_extra", {}) if agent_schema else {}
|
|
604
|
+
default_table = extra.get("default_search_table")
|
|
605
|
+
has_embeddings = extra.get("has_embeddings", False)
|
|
606
|
+
|
|
607
|
+
if default_table:
|
|
608
|
+
# Build description suffix for search_rem
|
|
609
|
+
search_rem_suffix = f"\n\nFor this schema, use `search_rem` to query `{default_table}`. "
|
|
610
|
+
if has_embeddings:
|
|
611
|
+
search_rem_suffix += f"SEARCH works well on {default_table} (has embeddings). "
|
|
612
|
+
search_rem_suffix += f"Example: `SEARCH \"your query\" FROM {default_table} LIMIT 10`"
|
|
613
|
+
|
|
614
|
+
# Add tools from MCP server (in-process, no subprocess)
|
|
492
615
|
if mcp_server_configs:
|
|
493
616
|
for server_config in mcp_server_configs:
|
|
494
617
|
server_type = server_config.get("type")
|
|
@@ -511,9 +634,17 @@ async def create_agent(
|
|
|
511
634
|
mcp_tools_dict = await mcp_server.get_tools()
|
|
512
635
|
|
|
513
636
|
for tool_name, tool_func in mcp_tools_dict.items():
|
|
514
|
-
|
|
637
|
+
# Add description suffix to search_rem tool if schema specifies a default table
|
|
638
|
+
tool_suffix = search_rem_suffix if tool_name == "search_rem" else None
|
|
639
|
+
|
|
640
|
+
wrapped_tool = create_mcp_tool_wrapper(
|
|
641
|
+
tool_name,
|
|
642
|
+
tool_func,
|
|
643
|
+
user_id=context.user_id if context else None,
|
|
644
|
+
description_suffix=tool_suffix,
|
|
645
|
+
)
|
|
515
646
|
tools.append(wrapped_tool)
|
|
516
|
-
logger.debug(f"Loaded MCP tool: {tool_name}")
|
|
647
|
+
logger.debug(f"Loaded MCP tool: {tool_name}" + (" (with schema suffix)" if tool_suffix else ""))
|
|
517
648
|
|
|
518
649
|
logger.info(f"Loaded {len(mcp_tools_dict)} tools from MCP server: {server_id} (in-process)")
|
|
519
650
|
|
|
@@ -522,18 +653,67 @@ async def create_agent(
|
|
|
522
653
|
else:
|
|
523
654
|
logger.warning(f"Unsupported MCP server type: {server_type}")
|
|
524
655
|
|
|
656
|
+
# Convert resources to tools (MCP convenience syntax)
|
|
657
|
+
# Resources declared in agent YAML become callable tools - eliminates
|
|
658
|
+
# the artificial MCP distinction between tools and resources
|
|
659
|
+
#
|
|
660
|
+
# Supports both concrete and template URIs:
|
|
661
|
+
# - Concrete: "rem://schemas" -> no-param tool
|
|
662
|
+
# - Template: "patient-profile://field/{field_key}" -> tool with field_key param
|
|
663
|
+
from ..mcp.tool_wrapper import create_resource_tool
|
|
664
|
+
|
|
665
|
+
# Collect all resource URIs from both resources section AND tools section
|
|
666
|
+
resource_uris = []
|
|
667
|
+
|
|
668
|
+
# From resources section (legacy format)
|
|
525
669
|
if resource_configs:
|
|
526
|
-
|
|
527
|
-
|
|
670
|
+
for resource_config in resource_configs:
|
|
671
|
+
if hasattr(resource_config, 'uri'):
|
|
672
|
+
uri = resource_config.uri
|
|
673
|
+
usage = resource_config.description or ""
|
|
674
|
+
else:
|
|
675
|
+
uri = resource_config.get("uri", "")
|
|
676
|
+
usage = resource_config.get("description", "")
|
|
677
|
+
if uri:
|
|
678
|
+
resource_uris.append((uri, usage))
|
|
679
|
+
|
|
680
|
+
# From tools section - detect URIs (anything with ://)
|
|
681
|
+
# This allows unified syntax: resources as tools
|
|
682
|
+
tool_configs = metadata.tools if metadata and hasattr(metadata, 'tools') else []
|
|
683
|
+
for tool_config in tool_configs:
|
|
684
|
+
if hasattr(tool_config, 'name'):
|
|
685
|
+
tool_name = tool_config.name
|
|
686
|
+
tool_desc = tool_config.description or ""
|
|
687
|
+
else:
|
|
688
|
+
tool_name = tool_config.get("name", "")
|
|
689
|
+
tool_desc = tool_config.get("description", "")
|
|
690
|
+
|
|
691
|
+
# Auto-detect resource URIs (anything with :// scheme)
|
|
692
|
+
if "://" in tool_name:
|
|
693
|
+
resource_uris.append((tool_name, tool_desc))
|
|
694
|
+
|
|
695
|
+
# Create tools from collected resource URIs
|
|
696
|
+
for uri, usage in resource_uris:
|
|
697
|
+
resource_tool = create_resource_tool(uri, usage)
|
|
698
|
+
tools.append(resource_tool)
|
|
699
|
+
logger.debug(f"Loaded resource as tool: {uri}")
|
|
528
700
|
|
|
529
701
|
# Create dynamic result_type from schema if not provided
|
|
702
|
+
# Note: use_structured_output is set earlier from metadata.structured_output
|
|
530
703
|
if result_type is None and agent_schema and "properties" in agent_schema:
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
704
|
+
if use_structured_output:
|
|
705
|
+
# Pre-process schema for Qwen compatibility (strips min/max, sets additionalProperties=False)
|
|
706
|
+
# This ensures the generated Pydantic model doesn't have incompatible constraints
|
|
707
|
+
sanitized_schema = _prepare_schema_for_qwen(agent_schema)
|
|
708
|
+
result_type = _create_model_from_schema(sanitized_schema)
|
|
709
|
+
logger.debug(f"Created dynamic Pydantic model: {result_type.__name__}")
|
|
710
|
+
else:
|
|
711
|
+
# Convert properties to prompt guidance instead of structured output
|
|
712
|
+
# This informs the agent about expected response structure without forcing it
|
|
713
|
+
properties_prompt = _convert_properties_to_prompt(agent_schema.get("properties", {}))
|
|
714
|
+
if properties_prompt:
|
|
715
|
+
system_prompt = system_prompt + "\n\n" + properties_prompt
|
|
716
|
+
logger.debug("Structured output disabled - properties converted to prompt guidance")
|
|
537
717
|
|
|
538
718
|
# Create agent with optional output_type for structured output and tools
|
|
539
719
|
if result_type:
|
|
@@ -541,21 +721,30 @@ async def create_agent(
|
|
|
541
721
|
wrapped_result_type = _create_schema_wrapper(
|
|
542
722
|
result_type, strip_description=strip_model_description
|
|
543
723
|
)
|
|
724
|
+
# Use InstrumentationSettings with version=3 to include agent name in span names
|
|
725
|
+
from pydantic_ai.models.instrumented import InstrumentationSettings
|
|
726
|
+
instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
|
|
727
|
+
|
|
544
728
|
agent = Agent(
|
|
545
729
|
model=model,
|
|
730
|
+
name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
|
|
546
731
|
system_prompt=system_prompt,
|
|
547
732
|
output_type=wrapped_result_type,
|
|
548
733
|
tools=tools,
|
|
549
|
-
instrument=
|
|
734
|
+
instrument=instrumentation,
|
|
550
735
|
model_settings={"temperature": temperature},
|
|
551
736
|
retries=settings.llm.max_retries,
|
|
552
737
|
)
|
|
553
738
|
else:
|
|
739
|
+
from pydantic_ai.models.instrumented import InstrumentationSettings
|
|
740
|
+
instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
|
|
741
|
+
|
|
554
742
|
agent = Agent(
|
|
555
743
|
model=model,
|
|
744
|
+
name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
|
|
556
745
|
system_prompt=system_prompt,
|
|
557
746
|
tools=tools,
|
|
558
|
-
instrument=
|
|
747
|
+
instrument=instrumentation,
|
|
559
748
|
model_settings={"temperature": temperature},
|
|
560
749
|
retries=settings.llm.max_retries,
|
|
561
750
|
)
|