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
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,92 @@ 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(
|
|
157
|
+
default="mcp-server",
|
|
85
158
|
description=(
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
159
|
+
"Server identifier for logging and debugging. "
|
|
160
|
+
"Defaults to 'mcp-server' if not specified. "
|
|
161
|
+
"Example: 'rem-local'"
|
|
89
162
|
)
|
|
90
163
|
)
|
|
91
164
|
|
|
@@ -130,6 +203,38 @@ class AgentSchemaMetadata(BaseModel):
|
|
|
130
203
|
),
|
|
131
204
|
)
|
|
132
205
|
|
|
206
|
+
# System prompt override (takes precedence over description when present)
|
|
207
|
+
system_prompt: str | None = Field(
|
|
208
|
+
default=None,
|
|
209
|
+
description=(
|
|
210
|
+
"Custom system prompt that overrides or extends the schema description. "
|
|
211
|
+
"When present, this is combined with the main schema.description field "
|
|
212
|
+
"to form the complete system prompt. Use this for detailed instructions "
|
|
213
|
+
"that you don't want in the public schema description."
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Structured output toggle
|
|
218
|
+
structured_output: bool = Field(
|
|
219
|
+
default=True,
|
|
220
|
+
description=(
|
|
221
|
+
"Whether to enforce structured JSON output. "
|
|
222
|
+
"When False, the agent produces free-form text and schema properties "
|
|
223
|
+
"are converted to prompt guidance instead. Default: True (JSON output)."
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# MCP server configurations (for dynamic tool loading)
|
|
228
|
+
mcp_servers: list[MCPServerConfig] = Field(
|
|
229
|
+
default_factory=list,
|
|
230
|
+
description=(
|
|
231
|
+
"MCP server configurations for dynamic tool loading. "
|
|
232
|
+
"Servers are loaded in-process at agent creation time. "
|
|
233
|
+
"All tools from configured servers become available to the agent. "
|
|
234
|
+
"If not specified, defaults to rem.mcp_server (REM's built-in tools)."
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
|
|
133
238
|
tools: list[MCPToolReference] = Field(
|
|
134
239
|
default_factory=list,
|
|
135
240
|
description=(
|
|
@@ -394,3 +499,238 @@ def create_agent_schema(
|
|
|
394
499
|
json_schema_extra=metadata.model_dump(),
|
|
395
500
|
**kwargs,
|
|
396
501
|
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# =============================================================================
|
|
505
|
+
# YAML and Database Serialization
|
|
506
|
+
# =============================================================================
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def schema_to_dict(schema: AgentSchema, exclude_none: bool = True) -> dict[str, Any]:
|
|
510
|
+
"""
|
|
511
|
+
Serialize AgentSchema to a dictionary suitable for YAML or database storage.
|
|
512
|
+
|
|
513
|
+
This produces the canonical format used in:
|
|
514
|
+
- YAML files (schemas/agents/*.yaml)
|
|
515
|
+
- Database spec column (schemas table)
|
|
516
|
+
- API responses
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
schema: AgentSchema instance to serialize
|
|
520
|
+
exclude_none: If True, omit None values from output
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Dictionary representation of the schema
|
|
524
|
+
|
|
525
|
+
Example:
|
|
526
|
+
>>> schema = AgentSchema(
|
|
527
|
+
... description="System prompt...",
|
|
528
|
+
... properties={"answer": {"type": "string"}},
|
|
529
|
+
... json_schema_extra={"name": "my-agent", "structured_output": False}
|
|
530
|
+
... )
|
|
531
|
+
>>> d = schema_to_dict(schema)
|
|
532
|
+
>>> d["json_schema_extra"]["name"]
|
|
533
|
+
"my-agent"
|
|
534
|
+
"""
|
|
535
|
+
return schema.model_dump(exclude_none=exclude_none)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def schema_from_dict(data: dict[str, Any]) -> AgentSchema:
|
|
539
|
+
"""
|
|
540
|
+
Deserialize a dictionary to AgentSchema.
|
|
541
|
+
|
|
542
|
+
This handles:
|
|
543
|
+
- YAML files loaded with yaml.safe_load()
|
|
544
|
+
- Database spec column (JSON)
|
|
545
|
+
- API request bodies
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
data: Dictionary containing schema data
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
Validated AgentSchema instance
|
|
552
|
+
|
|
553
|
+
Raises:
|
|
554
|
+
ValidationError: If data doesn't match schema structure
|
|
555
|
+
|
|
556
|
+
Example:
|
|
557
|
+
>>> data = {"type": "object", "description": "...", "properties": {}, "json_schema_extra": {"name": "test"}}
|
|
558
|
+
>>> schema = schema_from_dict(data)
|
|
559
|
+
>>> schema.json_schema_extra["name"]
|
|
560
|
+
"test"
|
|
561
|
+
"""
|
|
562
|
+
return AgentSchema.model_validate(data)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def schema_to_yaml(schema: AgentSchema) -> str:
|
|
566
|
+
"""
|
|
567
|
+
Serialize AgentSchema to YAML string.
|
|
568
|
+
|
|
569
|
+
The output format matches the canonical schema file format:
|
|
570
|
+
```yaml
|
|
571
|
+
type: object
|
|
572
|
+
description: |
|
|
573
|
+
System prompt here...
|
|
574
|
+
properties:
|
|
575
|
+
answer:
|
|
576
|
+
type: string
|
|
577
|
+
json_schema_extra:
|
|
578
|
+
name: my-agent
|
|
579
|
+
system_prompt: |
|
|
580
|
+
Extended prompt here...
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
schema: AgentSchema instance to serialize
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
YAML string representation
|
|
588
|
+
|
|
589
|
+
Example:
|
|
590
|
+
>>> schema = create_agent_schema(
|
|
591
|
+
... description="You are a test agent",
|
|
592
|
+
... properties={"answer": {"type": "string"}},
|
|
593
|
+
... required=["answer"],
|
|
594
|
+
... name="test-agent"
|
|
595
|
+
... )
|
|
596
|
+
>>> yaml_str = schema_to_yaml(schema)
|
|
597
|
+
>>> "test-agent" in yaml_str
|
|
598
|
+
True
|
|
599
|
+
"""
|
|
600
|
+
import yaml
|
|
601
|
+
|
|
602
|
+
return yaml.dump(
|
|
603
|
+
schema_to_dict(schema),
|
|
604
|
+
default_flow_style=False,
|
|
605
|
+
allow_unicode=True,
|
|
606
|
+
sort_keys=False,
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def schema_from_yaml(yaml_content: str) -> AgentSchema:
|
|
611
|
+
"""
|
|
612
|
+
Deserialize YAML string to AgentSchema.
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
yaml_content: YAML string containing schema definition
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
Validated AgentSchema instance
|
|
619
|
+
|
|
620
|
+
Raises:
|
|
621
|
+
yaml.YAMLError: If YAML parsing fails
|
|
622
|
+
ValidationError: If schema structure is invalid
|
|
623
|
+
|
|
624
|
+
Example:
|
|
625
|
+
>>> yaml_str = '''
|
|
626
|
+
... type: object
|
|
627
|
+
... description: Test agent
|
|
628
|
+
... properties:
|
|
629
|
+
... answer:
|
|
630
|
+
... type: string
|
|
631
|
+
... json_schema_extra:
|
|
632
|
+
... name: test
|
|
633
|
+
... '''
|
|
634
|
+
>>> schema = schema_from_yaml(yaml_str)
|
|
635
|
+
>>> schema.json_schema_extra["name"]
|
|
636
|
+
"test"
|
|
637
|
+
"""
|
|
638
|
+
import yaml
|
|
639
|
+
|
|
640
|
+
data = yaml.safe_load(yaml_content)
|
|
641
|
+
return schema_from_dict(data)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def schema_from_yaml_file(file_path: str) -> AgentSchema:
|
|
645
|
+
"""
|
|
646
|
+
Load AgentSchema from a YAML file.
|
|
647
|
+
|
|
648
|
+
Args:
|
|
649
|
+
file_path: Path to YAML file
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
Validated AgentSchema instance
|
|
653
|
+
|
|
654
|
+
Raises:
|
|
655
|
+
FileNotFoundError: If file doesn't exist
|
|
656
|
+
yaml.YAMLError: If YAML parsing fails
|
|
657
|
+
ValidationError: If schema structure is invalid
|
|
658
|
+
|
|
659
|
+
Example:
|
|
660
|
+
>>> schema = schema_from_yaml_file("schemas/agents/rem.yaml")
|
|
661
|
+
>>> schema.json_schema_extra["name"]
|
|
662
|
+
"rem"
|
|
663
|
+
"""
|
|
664
|
+
with open(file_path, "r") as f:
|
|
665
|
+
return schema_from_yaml(f.read())
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def get_system_prompt(schema: AgentSchema | dict[str, Any]) -> str:
|
|
669
|
+
"""
|
|
670
|
+
Extract the complete system prompt from a schema.
|
|
671
|
+
|
|
672
|
+
Combines:
|
|
673
|
+
1. schema.description (base system prompt / public description)
|
|
674
|
+
2. json_schema_extra.system_prompt (extended instructions if present)
|
|
675
|
+
|
|
676
|
+
Args:
|
|
677
|
+
schema: AgentSchema instance or raw dict
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
Complete system prompt string
|
|
681
|
+
|
|
682
|
+
Example:
|
|
683
|
+
>>> schema = AgentSchema(
|
|
684
|
+
... description="Base description",
|
|
685
|
+
... properties={},
|
|
686
|
+
... json_schema_extra={"name": "test", "system_prompt": "Extended instructions"}
|
|
687
|
+
... )
|
|
688
|
+
>>> prompt = get_system_prompt(schema)
|
|
689
|
+
>>> "Base description" in prompt and "Extended instructions" in prompt
|
|
690
|
+
True
|
|
691
|
+
"""
|
|
692
|
+
if isinstance(schema, dict):
|
|
693
|
+
base = schema.get("description", "")
|
|
694
|
+
extra = schema.get("json_schema_extra", {})
|
|
695
|
+
custom = extra.get("system_prompt") if isinstance(extra, dict) else None
|
|
696
|
+
else:
|
|
697
|
+
base = schema.description
|
|
698
|
+
extra = schema.json_schema_extra
|
|
699
|
+
if isinstance(extra, dict):
|
|
700
|
+
custom = extra.get("system_prompt")
|
|
701
|
+
elif isinstance(extra, AgentSchemaMetadata):
|
|
702
|
+
custom = extra.system_prompt
|
|
703
|
+
else:
|
|
704
|
+
custom = None
|
|
705
|
+
|
|
706
|
+
if custom:
|
|
707
|
+
return f"{base}\n\n{custom}" if base else custom
|
|
708
|
+
return base
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def get_metadata(schema: AgentSchema | dict[str, Any]) -> AgentSchemaMetadata:
|
|
712
|
+
"""
|
|
713
|
+
Extract and validate metadata from a schema.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
schema: AgentSchema instance or raw dict
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
Validated AgentSchemaMetadata instance
|
|
720
|
+
|
|
721
|
+
Example:
|
|
722
|
+
>>> schema = {"json_schema_extra": {"name": "test", "system_prompt": "hello"}}
|
|
723
|
+
>>> meta = get_metadata(schema)
|
|
724
|
+
>>> meta.name
|
|
725
|
+
"test"
|
|
726
|
+
>>> meta.system_prompt
|
|
727
|
+
"hello"
|
|
728
|
+
"""
|
|
729
|
+
if isinstance(schema, dict):
|
|
730
|
+
extra = schema.get("json_schema_extra", {})
|
|
731
|
+
else:
|
|
732
|
+
extra = schema.json_schema_extra
|
|
733
|
+
|
|
734
|
+
if isinstance(extra, AgentSchemaMetadata):
|
|
735
|
+
return extra
|
|
736
|
+
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']})"
|
rem/api/README.md
CHANGED
|
@@ -158,9 +158,70 @@ The dreaming worker runs periodically to build user models:
|
|
|
158
158
|
- User profile automatically loaded and injected into system message
|
|
159
159
|
- Simpler for basic chatbots that always need context
|
|
160
160
|
|
|
161
|
+
## Authentication
|
|
162
|
+
|
|
163
|
+
### Production Authentication
|
|
164
|
+
|
|
165
|
+
When `AUTH__ENABLED=true`, users authenticate via OAuth (Google or Microsoft). The OAuth flow:
|
|
166
|
+
|
|
167
|
+
1. User visits `/api/auth/google/login` or `/api/auth/microsoft/login`
|
|
168
|
+
2. User authenticates with provider
|
|
169
|
+
3. Callback stores user in session cookie
|
|
170
|
+
4. Subsequent requests use session cookie
|
|
171
|
+
|
|
172
|
+
### Development Token (Non-Production Only)
|
|
173
|
+
|
|
174
|
+
For local development and testing, you can use a dev token instead of OAuth. This endpoint is available at `/api/dev/token` whenever `ENVIRONMENT != "production"`, regardless of whether auth is enabled.
|
|
175
|
+
|
|
176
|
+
**Get Token:**
|
|
177
|
+
```bash
|
|
178
|
+
curl http://localhost:8000/api/dev/token
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Response:**
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"token": "dev_89737a19376332bfd9a4a06db8b79fd1",
|
|
185
|
+
"type": "Bearer",
|
|
186
|
+
"user": {
|
|
187
|
+
"id": "test-user",
|
|
188
|
+
"email": "test@rem.local",
|
|
189
|
+
"name": "Test User"
|
|
190
|
+
},
|
|
191
|
+
"usage": "curl -H \"Authorization: Bearer dev_...\" http://localhost:8000/api/v1/...",
|
|
192
|
+
"warning": "This token is for development/testing only and will not work in production."
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Use Token:**
|
|
197
|
+
```bash
|
|
198
|
+
# Get the token
|
|
199
|
+
TOKEN=$(curl -s http://localhost:8000/api/dev/token | jq -r .token)
|
|
200
|
+
|
|
201
|
+
# Use it in requests
|
|
202
|
+
curl -H "Authorization: Bearer $TOKEN" \
|
|
203
|
+
-H "X-Tenant-Id: default" \
|
|
204
|
+
http://localhost:8000/api/v1/shared-with-me
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Security Notes:**
|
|
208
|
+
- Only available when `ENVIRONMENT != "production"`
|
|
209
|
+
- Token is HMAC-signed using session secret
|
|
210
|
+
- Authenticates as `test-user` with `pro` tier and `admin` role
|
|
211
|
+
- Token is deterministic per environment (same secret = same token)
|
|
212
|
+
|
|
213
|
+
### Anonymous Access
|
|
214
|
+
|
|
215
|
+
When `AUTH__ALLOW_ANONYMOUS=true` (default in development):
|
|
216
|
+
- Requests without authentication are allowed
|
|
217
|
+
- Anonymous users get rate-limited access
|
|
218
|
+
- MCP endpoints still require auth unless `AUTH__MCP_REQUIRES_AUTH=false`
|
|
219
|
+
|
|
161
220
|
## Usage Examples
|
|
162
221
|
|
|
163
|
-
**Note on Authentication**: By default, authentication is disabled (`AUTH__ENABLED=false`) for local development and testing. The examples below work without an `Authorization` header. If authentication is enabled
|
|
222
|
+
**Note on Authentication**: By default, authentication is disabled (`AUTH__ENABLED=false`) for local development and testing. The examples below work without an `Authorization` header. If authentication is enabled, use either:
|
|
223
|
+
- **Dev token**: `-H "Authorization: Bearer $(curl -s http://localhost:8000/api/dev/token | jq -r .token)"`
|
|
224
|
+
- **Session cookie**: Login via OAuth first, then use cookies
|
|
164
225
|
|
|
165
226
|
### cURL: Simple Chat
|
|
166
227
|
|
|
@@ -363,6 +424,157 @@ data: {"id":"chatcmpl-abc123","choices":[{"delta":{},"finish_reason":"stop","ind
|
|
|
363
424
|
data: [DONE]
|
|
364
425
|
```
|
|
365
426
|
|
|
427
|
+
## Extended SSE Event Protocol
|
|
428
|
+
|
|
429
|
+
REM uses OpenAI-compatible format for text content streaming, plus custom named SSE events for rich UI interactions.
|
|
430
|
+
|
|
431
|
+
### Event Types
|
|
432
|
+
|
|
433
|
+
| Event Type | Format | Purpose | UI Display |
|
|
434
|
+
|------------|--------|---------|------------|
|
|
435
|
+
| (text content) | `data:` (OpenAI format) | Content chunks | Main response area |
|
|
436
|
+
| `reasoning` | `event:` | Model thinking | Collapsible "thinking" section |
|
|
437
|
+
| `progress` | `event:` | Step indicators | Progress bar/stepper |
|
|
438
|
+
| `tool_call` | `event:` | Tool invocations | Tool status panel |
|
|
439
|
+
| `action_request` | `event:` | User input solicitation | Buttons, forms, modals |
|
|
440
|
+
| `metadata` | `event:` | System info | Hidden or badge display |
|
|
441
|
+
| `error` | `event:` | Error notification | Error toast/alert |
|
|
442
|
+
| `done` | `event:` | Stream completion | Cleanup signal |
|
|
443
|
+
|
|
444
|
+
### Event Format
|
|
445
|
+
|
|
446
|
+
**Text content (OpenAI-compatible `data:` format):**
|
|
447
|
+
```
|
|
448
|
+
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1732748123,"model":"gpt-4","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello "},"finish_reason":null}]}
|
|
449
|
+
|
|
450
|
+
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1732748123,"model":"gpt-4","choices":[{"index":0,"delta":{"content":"world!"},"finish_reason":null}]}
|
|
451
|
+
|
|
452
|
+
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1732748123,"model":"gpt-4","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
|
453
|
+
|
|
454
|
+
data: [DONE]
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Named events (use `event:` prefix):**
|
|
458
|
+
```
|
|
459
|
+
event: reasoning
|
|
460
|
+
data: {"type": "reasoning", "content": "Analyzing the request...", "step": 1}
|
|
461
|
+
|
|
462
|
+
event: progress
|
|
463
|
+
data: {"type": "progress", "step": 1, "total_steps": 3, "label": "Searching", "status": "in_progress"}
|
|
464
|
+
|
|
465
|
+
event: tool_call
|
|
466
|
+
data: {"type": "tool_call", "tool_name": "search_rem", "status": "started", "arguments": {"query": "..."}}
|
|
467
|
+
|
|
468
|
+
event: action_request
|
|
469
|
+
data: {"type": "action_request", "card": {"id": "feedback-1", "prompt": "Was this helpful?", "actions": [...]}}
|
|
470
|
+
|
|
471
|
+
event: metadata
|
|
472
|
+
data: {"type": "metadata", "confidence": 0.95, "sources": ["doc1.md"], "hidden": false}
|
|
473
|
+
|
|
474
|
+
event: done
|
|
475
|
+
data: {"type": "done", "reason": "stop"}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Action Request Cards (Adaptive Cards-inspired)
|
|
479
|
+
|
|
480
|
+
Action requests solicit user input using a schema inspired by [Microsoft Adaptive Cards](https://adaptivecards.io/):
|
|
481
|
+
|
|
482
|
+
```json
|
|
483
|
+
{
|
|
484
|
+
"type": "action_request",
|
|
485
|
+
"card": {
|
|
486
|
+
"id": "confirm-delete-123",
|
|
487
|
+
"prompt": "Are you sure you want to delete this item?",
|
|
488
|
+
"display_style": "modal",
|
|
489
|
+
"actions": [
|
|
490
|
+
{
|
|
491
|
+
"type": "Action.Submit",
|
|
492
|
+
"id": "confirm",
|
|
493
|
+
"title": "Delete",
|
|
494
|
+
"style": "destructive",
|
|
495
|
+
"data": {"action": "delete", "item_id": "123"}
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
"type": "Action.Submit",
|
|
499
|
+
"id": "cancel",
|
|
500
|
+
"title": "Cancel",
|
|
501
|
+
"style": "secondary",
|
|
502
|
+
"data": {"action": "cancel"}
|
|
503
|
+
}
|
|
504
|
+
],
|
|
505
|
+
"inputs": [
|
|
506
|
+
{
|
|
507
|
+
"type": "Input.Text",
|
|
508
|
+
"id": "reason",
|
|
509
|
+
"label": "Reason (optional)",
|
|
510
|
+
"placeholder": "Why are you deleting this?"
|
|
511
|
+
}
|
|
512
|
+
],
|
|
513
|
+
"timeout_ms": 30000
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Action Types:**
|
|
519
|
+
- `Action.Submit` - Send data to server
|
|
520
|
+
- `Action.OpenUrl` - Navigate to URL
|
|
521
|
+
- `Action.ShowCard` - Reveal nested content
|
|
522
|
+
|
|
523
|
+
**Input Types:**
|
|
524
|
+
- `Input.Text` - Text field (single or multiline)
|
|
525
|
+
- `Input.ChoiceSet` - Dropdown/radio selection
|
|
526
|
+
- `Input.Toggle` - Checkbox/toggle
|
|
527
|
+
|
|
528
|
+
### SSE Simulator Endpoint
|
|
529
|
+
|
|
530
|
+
For frontend development and testing, use the simulator which generates all event types without LLM costs:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
curl -X POST http://localhost:8000/api/v1/chat/completions \
|
|
534
|
+
-H "Content-Type: application/json" \
|
|
535
|
+
-H "X-Agent-Schema: simulator" \
|
|
536
|
+
-d '{"messages": [{"role": "user", "content": "demo"}], "stream": true}'
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
The simulator produces a scripted sequence demonstrating:
|
|
540
|
+
1. Reasoning events (4 steps)
|
|
541
|
+
2. Progress indicators
|
|
542
|
+
3. Simulated tool calls
|
|
543
|
+
4. Rich markdown content
|
|
544
|
+
5. Metadata with confidence
|
|
545
|
+
6. Action request for feedback
|
|
546
|
+
|
|
547
|
+
See `rem/agentic/agents/sse_simulator.py` for implementation details.
|
|
548
|
+
|
|
549
|
+
### Frontend Integration
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// Parse SSE events in React/TypeScript
|
|
553
|
+
const eventSource = new EventSource('/api/v1/chat/completions');
|
|
554
|
+
|
|
555
|
+
eventSource.onmessage = (e) => {
|
|
556
|
+
// Default handler for data-only events (text_delta)
|
|
557
|
+
const event = JSON.parse(e.data);
|
|
558
|
+
if (event.type === 'text_delta') {
|
|
559
|
+
appendContent(event.content);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
eventSource.addEventListener('reasoning', (e) => {
|
|
564
|
+
const event = JSON.parse(e.data);
|
|
565
|
+
appendReasoning(event.content);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
eventSource.addEventListener('action_request', (e) => {
|
|
569
|
+
const event = JSON.parse(e.data);
|
|
570
|
+
showActionCard(event.card);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
eventSource.addEventListener('done', () => {
|
|
574
|
+
eventSource.close();
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
366
578
|
## Architecture
|
|
367
579
|
|
|
368
580
|
### Middleware Ordering
|
|
@@ -437,6 +649,8 @@ When a user exceeds their rate limit (based on their tier), the API returns a 42
|
|
|
437
649
|
## Related Documentation
|
|
438
650
|
|
|
439
651
|
- [Chat Router](routers/chat/completions.py) - Chat completions implementation
|
|
652
|
+
- [SSE Events](routers/chat/sse_events.py) - SSE event type definitions
|
|
653
|
+
- [SSE Simulator](../../agentic/agents/sse_simulator.py) - Event simulator for testing
|
|
440
654
|
- [MCP Router](mcp_router/server.py) - MCP server implementation
|
|
441
655
|
- [Agent Schemas](../../schemas/agents/) - Available agent schemas
|
|
442
656
|
- [Session Compression](../../services/session/compression.py) - Compression implementation
|