remdb 0.3.0__py3-none-any.whl → 0.3.114__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.
- rem/__init__.py +129 -2
- 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 +500 -0
- rem/agentic/context.py +28 -22
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/otel/setup.py +92 -4
- rem/agentic/providers/phoenix.py +32 -43
- rem/agentic/providers/pydantic_ai.py +142 -22
- rem/agentic/schema.py +358 -21
- rem/agentic/tools/rem_tools.py +3 -3
- rem/api/README.md +238 -1
- rem/api/deps.py +255 -0
- rem/api/main.py +151 -37
- rem/api/mcp_router/resources.py +1 -1
- rem/api/mcp_router/server.py +17 -2
- rem/api/mcp_router/tools.py +143 -7
- rem/api/middleware/tracking.py +172 -0
- rem/api/routers/admin.py +277 -0
- rem/api/routers/auth.py +124 -0
- rem/api/routers/chat/completions.py +152 -16
- rem/api/routers/chat/models.py +7 -3
- rem/api/routers/chat/sse_events.py +526 -0
- rem/api/routers/chat/streaming.py +608 -45
- rem/api/routers/dev.py +81 -0
- rem/api/routers/feedback.py +148 -0
- rem/api/routers/messages.py +473 -0
- rem/api/routers/models.py +78 -0
- rem/api/routers/query.py +357 -0
- rem/api/routers/shared_sessions.py +406 -0
- rem/auth/middleware.py +126 -27
- rem/cli/commands/README.md +201 -70
- rem/cli/commands/ask.py +13 -10
- rem/cli/commands/cluster.py +1359 -0
- rem/cli/commands/configure.py +4 -3
- rem/cli/commands/db.py +350 -137
- rem/cli/commands/experiments.py +76 -72
- rem/cli/commands/process.py +22 -15
- rem/cli/commands/scaffold.py +47 -0
- rem/cli/commands/schema.py +95 -49
- rem/cli/main.py +29 -6
- rem/config.py +2 -2
- rem/models/core/core_model.py +7 -1
- 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/models/entities/user.py +10 -3
- rem/registry.py +373 -0
- rem/schemas/agents/rem.yaml +7 -3
- rem/services/content/providers.py +94 -140
- rem/services/content/service.py +92 -20
- rem/services/dreaming/affinity_service.py +2 -16
- rem/services/dreaming/moment_service.py +2 -15
- rem/services/embeddings/api.py +24 -17
- rem/services/embeddings/worker.py +16 -16
- rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
- rem/services/phoenix/client.py +252 -19
- rem/services/postgres/README.md +159 -15
- rem/services/postgres/__init__.py +2 -1
- rem/services/postgres/diff_service.py +426 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
- rem/services/postgres/repository.py +132 -0
- rem/services/postgres/schema_generator.py +86 -5
- rem/services/postgres/service.py +6 -6
- rem/services/rate_limit.py +113 -0
- rem/services/rem/README.md +14 -0
- rem/services/rem/parser.py +44 -9
- rem/services/rem/service.py +36 -2
- rem/services/session/compression.py +17 -1
- rem/services/session/reload.py +1 -1
- rem/services/user_service.py +98 -0
- rem/settings.py +169 -17
- rem/sql/background_indexes.sql +21 -16
- rem/sql/migrations/001_install.sql +231 -54
- rem/sql/migrations/002_install_models.sql +457 -393
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/utils/constants.py +97 -0
- rem/utils/date_utils.py +228 -0
- rem/utils/embeddings.py +17 -4
- rem/utils/files.py +167 -0
- rem/utils/mime_types.py +158 -0
- rem/utils/model_helpers.py +156 -1
- rem/utils/schema_loader.py +191 -35
- rem/utils/sql_types.py +3 -1
- rem/utils/vision.py +9 -14
- rem/workers/README.md +14 -14
- rem/workers/db_maintainer.py +74 -0
- {remdb-0.3.0.dist-info → remdb-0.3.114.dist-info}/METADATA +303 -164
- {remdb-0.3.0.dist-info → remdb-0.3.114.dist-info}/RECORD +96 -70
- {remdb-0.3.0.dist-info → remdb-0.3.114.dist-info}/WHEEL +1 -1
- rem/sql/002_install_models.sql +0 -1068
- rem/sql/install_models.sql +0 -1038
- {remdb-0.3.0.dist-info → remdb-0.3.114.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
|
|
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') 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
|
-
|
|
478
|
-
|
|
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)},
|
|
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,7 @@ async def create_agent(
|
|
|
487
591
|
|
|
488
592
|
set_agent_resource_attributes(agent_schema=agent_schema)
|
|
489
593
|
|
|
490
|
-
#
|
|
491
|
-
tools = []
|
|
594
|
+
# Add tools from MCP server (in-process, no subprocess)
|
|
492
595
|
if mcp_server_configs:
|
|
493
596
|
for server_config in mcp_server_configs:
|
|
494
597
|
server_type = server_config.get("type")
|
|
@@ -527,13 +630,21 @@ async def create_agent(
|
|
|
527
630
|
pass
|
|
528
631
|
|
|
529
632
|
# Create dynamic result_type from schema if not provided
|
|
633
|
+
# Note: use_structured_output is set earlier from metadata.structured_output
|
|
530
634
|
if result_type is None and agent_schema and "properties" in agent_schema:
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
635
|
+
if use_structured_output:
|
|
636
|
+
# Pre-process schema for Qwen compatibility (strips min/max, sets additionalProperties=False)
|
|
637
|
+
# This ensures the generated Pydantic model doesn't have incompatible constraints
|
|
638
|
+
sanitized_schema = _prepare_schema_for_qwen(agent_schema)
|
|
639
|
+
result_type = _create_model_from_schema(sanitized_schema)
|
|
640
|
+
logger.debug(f"Created dynamic Pydantic model: {result_type.__name__}")
|
|
641
|
+
else:
|
|
642
|
+
# Convert properties to prompt guidance instead of structured output
|
|
643
|
+
# This informs the agent about expected response structure without forcing it
|
|
644
|
+
properties_prompt = _convert_properties_to_prompt(agent_schema.get("properties", {}))
|
|
645
|
+
if properties_prompt:
|
|
646
|
+
system_prompt = system_prompt + "\n\n" + properties_prompt
|
|
647
|
+
logger.debug("Structured output disabled - properties converted to prompt guidance")
|
|
537
648
|
|
|
538
649
|
# Create agent with optional output_type for structured output and tools
|
|
539
650
|
if result_type:
|
|
@@ -541,21 +652,30 @@ async def create_agent(
|
|
|
541
652
|
wrapped_result_type = _create_schema_wrapper(
|
|
542
653
|
result_type, strip_description=strip_model_description
|
|
543
654
|
)
|
|
655
|
+
# Use InstrumentationSettings with version=3 to include agent name in span names
|
|
656
|
+
from pydantic_ai.models.instrumented import InstrumentationSettings
|
|
657
|
+
instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
|
|
658
|
+
|
|
544
659
|
agent = Agent(
|
|
545
660
|
model=model,
|
|
661
|
+
name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
|
|
546
662
|
system_prompt=system_prompt,
|
|
547
663
|
output_type=wrapped_result_type,
|
|
548
664
|
tools=tools,
|
|
549
|
-
instrument=
|
|
665
|
+
instrument=instrumentation,
|
|
550
666
|
model_settings={"temperature": temperature},
|
|
551
667
|
retries=settings.llm.max_retries,
|
|
552
668
|
)
|
|
553
669
|
else:
|
|
670
|
+
from pydantic_ai.models.instrumented import InstrumentationSettings
|
|
671
|
+
instrumentation = InstrumentationSettings(version=3) if settings.otel.enabled else False
|
|
672
|
+
|
|
554
673
|
agent = Agent(
|
|
555
674
|
model=model,
|
|
675
|
+
name=agent_name, # Used for OTEL span names (version 3: "invoke_agent {name}")
|
|
556
676
|
system_prompt=system_prompt,
|
|
557
677
|
tools=tools,
|
|
558
|
-
instrument=
|
|
678
|
+
instrument=instrumentation,
|
|
559
679
|
model_settings={"temperature": temperature},
|
|
560
680
|
retries=settings.llm.max_retries,
|
|
561
681
|
)
|
rem/agentic/schema.py
CHANGED
|
@@ -13,7 +13,7 @@ The schema protocol serves as:
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
from typing import Any, Literal
|
|
16
|
-
from pydantic import BaseModel, Field
|
|
16
|
+
from pydantic import BaseModel, Field, field_validator
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class MCPToolReference(BaseModel):
|
|
@@ -23,11 +23,21 @@ class MCPToolReference(BaseModel):
|
|
|
23
23
|
Tools are functions that agents can call during execution to
|
|
24
24
|
interact with external systems, retrieve data, or perform actions.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Two usage patterns:
|
|
27
|
+
1. With mcp_servers config: Just declare name + description, tools loaded from MCP servers
|
|
28
|
+
2. Explicit MCP server: Specify mcp_server to load tool from specific server
|
|
29
|
+
|
|
30
|
+
Example (declarative with mcp_servers):
|
|
31
|
+
{
|
|
32
|
+
"name": "search_rem",
|
|
33
|
+
"description": "Execute REM queries for entity lookup and search"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Example (explicit server):
|
|
27
37
|
{
|
|
28
38
|
"name": "lookup_entity",
|
|
29
39
|
"mcp_server": "rem",
|
|
30
|
-
"description": "Lookup entities by exact key
|
|
40
|
+
"description": "Lookup entities by exact key"
|
|
31
41
|
}
|
|
32
42
|
"""
|
|
33
43
|
|
|
@@ -38,20 +48,20 @@ class MCPToolReference(BaseModel):
|
|
|
38
48
|
)
|
|
39
49
|
)
|
|
40
50
|
|
|
41
|
-
mcp_server: str = Field(
|
|
51
|
+
mcp_server: str | None = Field(
|
|
52
|
+
default=None,
|
|
42
53
|
description=(
|
|
43
|
-
"MCP server identifier
|
|
44
|
-
"
|
|
45
|
-
"
|
|
54
|
+
"MCP server identifier (optional when using mcp_servers config). "
|
|
55
|
+
"If not specified, tool is expected from configured mcp_servers. "
|
|
56
|
+
"Resolved via environment variable: MCP_SERVER_{NAME} or MCP__{NAME}__URL."
|
|
46
57
|
)
|
|
47
58
|
)
|
|
48
59
|
|
|
49
60
|
description: str | None = Field(
|
|
50
61
|
default=None,
|
|
51
62
|
description=(
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"Use this to provide agent-specific guidance on tool usage."
|
|
63
|
+
"Tool description for the agent. Explains what the tool does "
|
|
64
|
+
"and when to use it. This is visible to the LLM."
|
|
55
65
|
),
|
|
56
66
|
)
|
|
57
67
|
|
|
@@ -63,29 +73,90 @@ class MCPResourceReference(BaseModel):
|
|
|
63
73
|
Resources are data sources that can be read by agents, such as
|
|
64
74
|
knowledge graph entities, files, or API endpoints.
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
Two formats supported:
|
|
77
|
+
1. uri: Exact URI or URI with query params
|
|
78
|
+
2. uri_pattern: Regex pattern for flexible matching
|
|
79
|
+
|
|
80
|
+
Example (exact URI):
|
|
81
|
+
{
|
|
82
|
+
"uri": "rem://schemas",
|
|
83
|
+
"name": "Agent Schemas",
|
|
84
|
+
"description": "List all available agent schemas"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Example (pattern):
|
|
67
88
|
{
|
|
68
89
|
"uri_pattern": "rem://resources/.*",
|
|
69
90
|
"mcp_server": "rem"
|
|
70
91
|
}
|
|
71
92
|
"""
|
|
72
93
|
|
|
73
|
-
|
|
94
|
+
# Support both exact URI and pattern
|
|
95
|
+
uri: str | None = Field(
|
|
96
|
+
default=None,
|
|
97
|
+
description=(
|
|
98
|
+
"Exact resource URI or URI with query parameters. "
|
|
99
|
+
"Examples: 'rem://schemas', 'rem://resources?category=drug.*'"
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
uri_pattern: str | None = Field(
|
|
104
|
+
default=None,
|
|
74
105
|
description=(
|
|
75
106
|
"Regex pattern matching resource URIs. "
|
|
76
|
-
"Examples: "
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
"Examples: 'rem://resources/.*' (all resources). "
|
|
108
|
+
"Use uri for exact URIs, uri_pattern for regex matching."
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
name: str | None = Field(
|
|
113
|
+
default=None,
|
|
114
|
+
description="Human-readable name for the resource."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
description: str | None = Field(
|
|
118
|
+
default=None,
|
|
119
|
+
description="Description of what the resource provides."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
mcp_server: str | None = Field(
|
|
123
|
+
default=None,
|
|
124
|
+
description=(
|
|
125
|
+
"MCP server identifier (optional when using mcp_servers config). "
|
|
126
|
+
"Resolved via environment variable MCP_SERVER_{NAME}."
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class MCPServerConfig(BaseModel):
|
|
132
|
+
"""
|
|
133
|
+
MCP server configuration for in-process tool loading.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
{
|
|
137
|
+
"type": "local",
|
|
138
|
+
"module": "rem.mcp_server",
|
|
139
|
+
"id": "rem-local"
|
|
140
|
+
}
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
type: Literal["local"] = Field(
|
|
144
|
+
default="local",
|
|
145
|
+
description="Server type. Currently only 'local' (in-process) is supported.",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
module: str = Field(
|
|
149
|
+
description=(
|
|
150
|
+
"Python module path containing the MCP server. "
|
|
151
|
+
"The module must export an 'mcp' object that supports get_tools(). "
|
|
152
|
+
"Example: 'rem.mcp_server'"
|
|
81
153
|
)
|
|
82
154
|
)
|
|
83
155
|
|
|
84
|
-
|
|
156
|
+
id: str = Field(
|
|
85
157
|
description=(
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"The server must expose resources matching the uri_pattern."
|
|
158
|
+
"Server identifier for logging and debugging. "
|
|
159
|
+
"Example: 'rem-local'"
|
|
89
160
|
)
|
|
90
161
|
)
|
|
91
162
|
|
|
@@ -130,6 +201,37 @@ class AgentSchemaMetadata(BaseModel):
|
|
|
130
201
|
),
|
|
131
202
|
)
|
|
132
203
|
|
|
204
|
+
# System prompt override (takes precedence over description when present)
|
|
205
|
+
system_prompt: str | None = Field(
|
|
206
|
+
default=None,
|
|
207
|
+
description=(
|
|
208
|
+
"Custom system prompt that overrides or extends the schema description. "
|
|
209
|
+
"When present, this is combined with the main schema.description field "
|
|
210
|
+
"to form the complete system prompt. Use this for detailed instructions "
|
|
211
|
+
"that you don't want in the public schema description."
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Structured output toggle
|
|
216
|
+
structured_output: bool = Field(
|
|
217
|
+
default=True,
|
|
218
|
+
description=(
|
|
219
|
+
"Whether to enforce structured JSON output. "
|
|
220
|
+
"When False, the agent produces free-form text and schema properties "
|
|
221
|
+
"are converted to prompt guidance instead. Default: True (JSON output)."
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# MCP server configurations (for dynamic tool loading)
|
|
226
|
+
mcp_servers: list[MCPServerConfig] = Field(
|
|
227
|
+
default_factory=list,
|
|
228
|
+
description=(
|
|
229
|
+
"MCP server configurations for dynamic tool loading. "
|
|
230
|
+
"Servers are loaded in-process at agent creation time. "
|
|
231
|
+
"All tools from configured servers become available to the agent."
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
|
|
133
235
|
tools: list[MCPToolReference] = Field(
|
|
134
236
|
default_factory=list,
|
|
135
237
|
description=(
|
|
@@ -394,3 +496,238 @@ def create_agent_schema(
|
|
|
394
496
|
json_schema_extra=metadata.model_dump(),
|
|
395
497
|
**kwargs,
|
|
396
498
|
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# =============================================================================
|
|
502
|
+
# YAML and Database Serialization
|
|
503
|
+
# =============================================================================
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def schema_to_dict(schema: AgentSchema, exclude_none: bool = True) -> dict[str, Any]:
|
|
507
|
+
"""
|
|
508
|
+
Serialize AgentSchema to a dictionary suitable for YAML or database storage.
|
|
509
|
+
|
|
510
|
+
This produces the canonical format used in:
|
|
511
|
+
- YAML files (schemas/agents/*.yaml)
|
|
512
|
+
- Database spec column (schemas table)
|
|
513
|
+
- API responses
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
schema: AgentSchema instance to serialize
|
|
517
|
+
exclude_none: If True, omit None values from output
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
Dictionary representation of the schema
|
|
521
|
+
|
|
522
|
+
Example:
|
|
523
|
+
>>> schema = AgentSchema(
|
|
524
|
+
... description="System prompt...",
|
|
525
|
+
... properties={"answer": {"type": "string"}},
|
|
526
|
+
... json_schema_extra={"name": "my-agent", "structured_output": False}
|
|
527
|
+
... )
|
|
528
|
+
>>> d = schema_to_dict(schema)
|
|
529
|
+
>>> d["json_schema_extra"]["name"]
|
|
530
|
+
"my-agent"
|
|
531
|
+
"""
|
|
532
|
+
return schema.model_dump(exclude_none=exclude_none)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def schema_from_dict(data: dict[str, Any]) -> AgentSchema:
|
|
536
|
+
"""
|
|
537
|
+
Deserialize a dictionary to AgentSchema.
|
|
538
|
+
|
|
539
|
+
This handles:
|
|
540
|
+
- YAML files loaded with yaml.safe_load()
|
|
541
|
+
- Database spec column (JSON)
|
|
542
|
+
- API request bodies
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
data: Dictionary containing schema data
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
Validated AgentSchema instance
|
|
549
|
+
|
|
550
|
+
Raises:
|
|
551
|
+
ValidationError: If data doesn't match schema structure
|
|
552
|
+
|
|
553
|
+
Example:
|
|
554
|
+
>>> data = {"type": "object", "description": "...", "properties": {}, "json_schema_extra": {"name": "test"}}
|
|
555
|
+
>>> schema = schema_from_dict(data)
|
|
556
|
+
>>> schema.json_schema_extra["name"]
|
|
557
|
+
"test"
|
|
558
|
+
"""
|
|
559
|
+
return AgentSchema.model_validate(data)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def schema_to_yaml(schema: AgentSchema) -> str:
|
|
563
|
+
"""
|
|
564
|
+
Serialize AgentSchema to YAML string.
|
|
565
|
+
|
|
566
|
+
The output format matches the canonical schema file format:
|
|
567
|
+
```yaml
|
|
568
|
+
type: object
|
|
569
|
+
description: |
|
|
570
|
+
System prompt here...
|
|
571
|
+
properties:
|
|
572
|
+
answer:
|
|
573
|
+
type: string
|
|
574
|
+
json_schema_extra:
|
|
575
|
+
name: my-agent
|
|
576
|
+
system_prompt: |
|
|
577
|
+
Extended prompt here...
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
Args:
|
|
581
|
+
schema: AgentSchema instance to serialize
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
YAML string representation
|
|
585
|
+
|
|
586
|
+
Example:
|
|
587
|
+
>>> schema = create_agent_schema(
|
|
588
|
+
... description="You are a test agent",
|
|
589
|
+
... properties={"answer": {"type": "string"}},
|
|
590
|
+
... required=["answer"],
|
|
591
|
+
... name="test-agent"
|
|
592
|
+
... )
|
|
593
|
+
>>> yaml_str = schema_to_yaml(schema)
|
|
594
|
+
>>> "test-agent" in yaml_str
|
|
595
|
+
True
|
|
596
|
+
"""
|
|
597
|
+
import yaml
|
|
598
|
+
|
|
599
|
+
return yaml.dump(
|
|
600
|
+
schema_to_dict(schema),
|
|
601
|
+
default_flow_style=False,
|
|
602
|
+
allow_unicode=True,
|
|
603
|
+
sort_keys=False,
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def schema_from_yaml(yaml_content: str) -> AgentSchema:
|
|
608
|
+
"""
|
|
609
|
+
Deserialize YAML string to AgentSchema.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
yaml_content: YAML string containing schema definition
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
Validated AgentSchema instance
|
|
616
|
+
|
|
617
|
+
Raises:
|
|
618
|
+
yaml.YAMLError: If YAML parsing fails
|
|
619
|
+
ValidationError: If schema structure is invalid
|
|
620
|
+
|
|
621
|
+
Example:
|
|
622
|
+
>>> yaml_str = '''
|
|
623
|
+
... type: object
|
|
624
|
+
... description: Test agent
|
|
625
|
+
... properties:
|
|
626
|
+
... answer:
|
|
627
|
+
... type: string
|
|
628
|
+
... json_schema_extra:
|
|
629
|
+
... name: test
|
|
630
|
+
... '''
|
|
631
|
+
>>> schema = schema_from_yaml(yaml_str)
|
|
632
|
+
>>> schema.json_schema_extra["name"]
|
|
633
|
+
"test"
|
|
634
|
+
"""
|
|
635
|
+
import yaml
|
|
636
|
+
|
|
637
|
+
data = yaml.safe_load(yaml_content)
|
|
638
|
+
return schema_from_dict(data)
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def schema_from_yaml_file(file_path: str) -> AgentSchema:
|
|
642
|
+
"""
|
|
643
|
+
Load AgentSchema from a YAML file.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
file_path: Path to YAML file
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
Validated AgentSchema instance
|
|
650
|
+
|
|
651
|
+
Raises:
|
|
652
|
+
FileNotFoundError: If file doesn't exist
|
|
653
|
+
yaml.YAMLError: If YAML parsing fails
|
|
654
|
+
ValidationError: If schema structure is invalid
|
|
655
|
+
|
|
656
|
+
Example:
|
|
657
|
+
>>> schema = schema_from_yaml_file("schemas/agents/rem.yaml")
|
|
658
|
+
>>> schema.json_schema_extra["name"]
|
|
659
|
+
"rem"
|
|
660
|
+
"""
|
|
661
|
+
with open(file_path, "r") as f:
|
|
662
|
+
return schema_from_yaml(f.read())
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def get_system_prompt(schema: AgentSchema | dict[str, Any]) -> str:
|
|
666
|
+
"""
|
|
667
|
+
Extract the complete system prompt from a schema.
|
|
668
|
+
|
|
669
|
+
Combines:
|
|
670
|
+
1. schema.description (base system prompt / public description)
|
|
671
|
+
2. json_schema_extra.system_prompt (extended instructions if present)
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
schema: AgentSchema instance or raw dict
|
|
675
|
+
|
|
676
|
+
Returns:
|
|
677
|
+
Complete system prompt string
|
|
678
|
+
|
|
679
|
+
Example:
|
|
680
|
+
>>> schema = AgentSchema(
|
|
681
|
+
... description="Base description",
|
|
682
|
+
... properties={},
|
|
683
|
+
... json_schema_extra={"name": "test", "system_prompt": "Extended instructions"}
|
|
684
|
+
... )
|
|
685
|
+
>>> prompt = get_system_prompt(schema)
|
|
686
|
+
>>> "Base description" in prompt and "Extended instructions" in prompt
|
|
687
|
+
True
|
|
688
|
+
"""
|
|
689
|
+
if isinstance(schema, dict):
|
|
690
|
+
base = schema.get("description", "")
|
|
691
|
+
extra = schema.get("json_schema_extra", {})
|
|
692
|
+
custom = extra.get("system_prompt") if isinstance(extra, dict) else None
|
|
693
|
+
else:
|
|
694
|
+
base = schema.description
|
|
695
|
+
extra = schema.json_schema_extra
|
|
696
|
+
if isinstance(extra, dict):
|
|
697
|
+
custom = extra.get("system_prompt")
|
|
698
|
+
elif isinstance(extra, AgentSchemaMetadata):
|
|
699
|
+
custom = extra.system_prompt
|
|
700
|
+
else:
|
|
701
|
+
custom = None
|
|
702
|
+
|
|
703
|
+
if custom:
|
|
704
|
+
return f"{base}\n\n{custom}" if base else custom
|
|
705
|
+
return base
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def get_metadata(schema: AgentSchema | dict[str, Any]) -> AgentSchemaMetadata:
|
|
709
|
+
"""
|
|
710
|
+
Extract and validate metadata from a schema.
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
schema: AgentSchema instance or raw dict
|
|
714
|
+
|
|
715
|
+
Returns:
|
|
716
|
+
Validated AgentSchemaMetadata instance
|
|
717
|
+
|
|
718
|
+
Example:
|
|
719
|
+
>>> schema = {"json_schema_extra": {"name": "test", "system_prompt": "hello"}}
|
|
720
|
+
>>> meta = get_metadata(schema)
|
|
721
|
+
>>> meta.name
|
|
722
|
+
"test"
|
|
723
|
+
>>> meta.system_prompt
|
|
724
|
+
"hello"
|
|
725
|
+
"""
|
|
726
|
+
if isinstance(schema, dict):
|
|
727
|
+
extra = schema.get("json_schema_extra", {})
|
|
728
|
+
else:
|
|
729
|
+
extra = schema.json_schema_extra
|
|
730
|
+
|
|
731
|
+
if isinstance(extra, AgentSchemaMetadata):
|
|
732
|
+
return extra
|
|
733
|
+
return AgentSchemaMetadata.model_validate(extra)
|
rem/agentic/tools/rem_tools.py
CHANGED
|
@@ -162,10 +162,10 @@ async def search_rem_tool(
|
|
|
162
162
|
return {"status": "error", "error": f"Unknown query_type: {query_type}"}
|
|
163
163
|
|
|
164
164
|
# Execute query
|
|
165
|
-
logger.
|
|
165
|
+
logger.debug(f"Executing REM query: {query_type} for user {user_id}")
|
|
166
166
|
result = await rem_service.execute_query(query)
|
|
167
167
|
|
|
168
|
-
logger.
|
|
168
|
+
logger.debug(f"Query completed: {query_type}")
|
|
169
169
|
return {
|
|
170
170
|
"status": "success",
|
|
171
171
|
"query_type": query_type,
|
|
@@ -212,7 +212,7 @@ async def ingest_file_tool(
|
|
|
212
212
|
is_local_server=is_local_server,
|
|
213
213
|
)
|
|
214
214
|
|
|
215
|
-
logger.
|
|
215
|
+
logger.debug(
|
|
216
216
|
f"File ingestion complete: {result['file_name']} "
|
|
217
217
|
f"(status: {result['processing_status']}, "
|
|
218
218
|
f"resources: {result['resources_created']})"
|