remdb 0.3.242__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 -0
- rem/agentic/README.md +760 -0
- rem/agentic/__init__.py +54 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +38 -0
- rem/agentic/agents/agent_manager.py +311 -0
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +425 -0
- rem/agentic/context_builder.py +360 -0
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +273 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +240 -0
- rem/agentic/providers/phoenix.py +926 -0
- rem/agentic/providers/pydantic_ai.py +854 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +737 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +242 -0
- rem/api/README.md +657 -0
- rem/api/deps.py +253 -0
- rem/api/main.py +460 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +820 -0
- rem/api/mcp_router/server.py +243 -0
- rem/api/mcp_router/tools.py +1605 -0
- rem/api/middleware/tracking.py +172 -0
- rem/api/routers/admin.py +520 -0
- rem/api/routers/auth.py +898 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/child_streaming.py +394 -0
- rem/api/routers/chat/completions.py +702 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +202 -0
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +546 -0
- rem/api/routers/chat/streaming.py +950 -0
- rem/api/routers/chat/streaming_utils.py +327 -0
- rem/api/routers/common.py +18 -0
- rem/api/routers/dev.py +87 -0
- rem/api/routers/feedback.py +276 -0
- rem/api/routers/messages.py +620 -0
- rem/api/routers/models.py +86 -0
- rem/api/routers/query.py +362 -0
- rem/api/routers/shared_sessions.py +422 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +36 -0
- rem/auth/jwt.py +367 -0
- rem/auth/middleware.py +318 -0
- rem/auth/providers/__init__.py +16 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/email.py +215 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +517 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +299 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +549 -0
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +495 -0
- rem/cli/commands/db.py +828 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1698 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +388 -0
- rem/cli/commands/query.py +109 -0
- rem/cli/commands/scaffold.py +47 -0
- rem/cli/commands/schema.py +230 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/commands/session.py +453 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +123 -0
- rem/config.py +244 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +70 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +672 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +246 -0
- rem/models/entities/__init__.py +68 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +64 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +181 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/session.py +84 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/subscriber.py +175 -0
- rem/models/entities/user.py +93 -0
- rem/py.typed +0 -0
- rem/registry.py +373 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/agent-builder.yaml +235 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +132 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +18 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +760 -0
- rem/services/content/service.py +762 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +322 -0
- rem/services/dreaming/moment_service.py +251 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/email/__init__.py +10 -0
- rem/services/email/service.py +522 -0
- rem/services/email/templates.py +360 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +127 -0
- rem/services/embeddings/worker.py +435 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +960 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +757 -0
- rem/services/postgres/__init__.py +49 -0
- rem/services/postgres/diff_service.py +599 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/programmable_diff_service.py +635 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
- rem/services/postgres/register_type.py +353 -0
- rem/services/postgres/repository.py +481 -0
- rem/services/postgres/schema_generator.py +661 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +355 -0
- rem/services/rate_limit.py +113 -0
- rem/services/rem/README.md +318 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +180 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +608 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +13 -0
- rem/services/session/compression.py +488 -0
- rem/services/session/pydantic_messages.py +310 -0
- rem/services/session/reload.py +85 -0
- rem/services/user_service.py +130 -0
- rem/settings.py +1877 -0
- rem/sql/background_indexes.sql +52 -0
- rem/sql/migrations/001_install.sql +983 -0
- rem/sql/migrations/002_install_models.sql +3157 -0
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +282 -0
- rem/sql/migrations/005_schema_update.sql +145 -0
- rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +628 -0
- rem/utils/__init__.py +61 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/constants.py +97 -0
- rem/utils/date_utils.py +228 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +436 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/files.py +323 -0
- rem/utils/markdown.py +16 -0
- rem/utils/mime_types.py +158 -0
- rem/utils/model_helpers.py +492 -0
- rem/utils/schema_loader.py +649 -0
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +350 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +325 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +7 -0
- rem/workers/db_listener.py +579 -0
- rem/workers/db_maintainer.py +74 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- rem/workers/unlogged_maintainer.py +463 -0
- remdb-0.3.242.dist-info/METADATA +1632 -0
- remdb-0.3.242.dist-info/RECORD +235 -0
- remdb-0.3.242.dist-info/WHEEL +4 -0
- remdb-0.3.242.dist-info/entry_points.txt +2 -0
rem/agentic/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REM Agentic Framework.
|
|
3
|
+
|
|
4
|
+
Provider-agnostic agent orchestration with JSON Schema agents,
|
|
5
|
+
MCP tool integration, and structured output.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .context import AgentContext
|
|
9
|
+
from .query import AgentQuery
|
|
10
|
+
from .schema import (
|
|
11
|
+
AgentSchema,
|
|
12
|
+
AgentSchemaMetadata,
|
|
13
|
+
MCPToolReference,
|
|
14
|
+
MCPResourceReference,
|
|
15
|
+
validate_agent_schema,
|
|
16
|
+
create_agent_schema,
|
|
17
|
+
)
|
|
18
|
+
from .providers.pydantic_ai import create_agent_from_schema_file, create_agent, AgentRuntime
|
|
19
|
+
from .query_helper import ask_rem, REMQueryOutput
|
|
20
|
+
from .llm_provider_models import (
|
|
21
|
+
ModelInfo,
|
|
22
|
+
AVAILABLE_MODELS,
|
|
23
|
+
ALLOWED_MODEL_IDS,
|
|
24
|
+
is_valid_model,
|
|
25
|
+
get_valid_model_or_default,
|
|
26
|
+
get_model_by_id,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
# Context and Query
|
|
31
|
+
"AgentContext",
|
|
32
|
+
"AgentQuery",
|
|
33
|
+
# Schema Protocol
|
|
34
|
+
"AgentSchema",
|
|
35
|
+
"AgentSchemaMetadata",
|
|
36
|
+
"MCPToolReference",
|
|
37
|
+
"MCPResourceReference",
|
|
38
|
+
"validate_agent_schema",
|
|
39
|
+
"create_agent_schema",
|
|
40
|
+
# Agent Factories
|
|
41
|
+
"create_agent_from_schema_file",
|
|
42
|
+
"create_agent",
|
|
43
|
+
"AgentRuntime",
|
|
44
|
+
# REM Query Helpers
|
|
45
|
+
"ask_rem",
|
|
46
|
+
"REMQueryOutput",
|
|
47
|
+
# LLM Provider Models
|
|
48
|
+
"ModelInfo",
|
|
49
|
+
"AVAILABLE_MODELS",
|
|
50
|
+
"ALLOWED_MODEL_IDS",
|
|
51
|
+
"is_valid_model",
|
|
52
|
+
"get_valid_model_or_default",
|
|
53
|
+
"get_model_by_id",
|
|
54
|
+
]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# REM Agents
|
|
2
|
+
|
|
3
|
+
Built-in agents for REM system operations.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This folder contains specialized agents that provide high-level interfaces for REM operations. These agents use LLMs to interpret natural language and convert it to structured REM queries.
|
|
8
|
+
|
|
9
|
+
## REM Query Agent
|
|
10
|
+
|
|
11
|
+
**File**: `rem_query_agent.py`
|
|
12
|
+
|
|
13
|
+
Converts natural language questions into structured REM queries with PostgreSQL dialect awareness.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- **Query Type Selection**: Automatically chooses optimal query type (LOOKUP, FUZZY, SEARCH, SQL, TRAVERSE)
|
|
18
|
+
- **PostgreSQL Dialect Aware**: Knows when to use KV_STORE vs primary tables
|
|
19
|
+
- **Token Optimized**: Minimal output fields for fast generation and low cost
|
|
20
|
+
- **Confidence Scoring**: Returns confidence (0-1) with reasoning for low scores
|
|
21
|
+
- **Multi-Step Planning**: Can break complex queries into multiple REM calls
|
|
22
|
+
|
|
23
|
+
### Usage
|
|
24
|
+
|
|
25
|
+
#### Simple Query
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from rem.agentic.agents import ask_rem
|
|
29
|
+
|
|
30
|
+
# Convert natural language to REM query
|
|
31
|
+
result = await ask_rem("Show me Sarah Chen")
|
|
32
|
+
|
|
33
|
+
print(result.query_type) # QueryType.LOOKUP
|
|
34
|
+
print(result.parameters) # {"entity_key": "sarah-chen"}
|
|
35
|
+
print(result.confidence) # 1.0
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### With Custom Model
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
# Use fast, cheap model for query generation
|
|
42
|
+
result = await ask_rem(
|
|
43
|
+
"Find documents about databases",
|
|
44
|
+
llm_model="gpt-4o-mini"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
print(result.query_type) # QueryType.SEARCH
|
|
48
|
+
print(result.parameters)
|
|
49
|
+
# {
|
|
50
|
+
# "query_text": "database",
|
|
51
|
+
# "table_name": "resources",
|
|
52
|
+
# "field_name": "content",
|
|
53
|
+
# "limit": 10
|
|
54
|
+
# }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### Integration with RemService
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from rem.services.rem import RemService
|
|
61
|
+
|
|
62
|
+
# RemService automatically uses REM Query Agent
|
|
63
|
+
result = await rem_service.ask_rem(
|
|
64
|
+
natural_query="What does Sarah manage?",
|
|
65
|
+
tenant_id="acme-corp"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Returns:
|
|
69
|
+
# {
|
|
70
|
+
# "query_output": {
|
|
71
|
+
# "query_type": "TRAVERSE",
|
|
72
|
+
# "parameters": {"start_key": "sarah-chen", "max_depth": 1, "rel_type": "manages"},
|
|
73
|
+
# "confidence": 0.85,
|
|
74
|
+
# "reasoning": "TRAVERSE query to find entities Sarah manages via graph edges"
|
|
75
|
+
# },
|
|
76
|
+
# "results": [...], # Executed query results (if confidence >= 0.7)
|
|
77
|
+
# "natural_query": "What does Sarah manage?"
|
|
78
|
+
# }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Query Types
|
|
82
|
+
|
|
83
|
+
| Type | Description | When to Use | Example |
|
|
84
|
+
|------|-------------|-------------|---------|
|
|
85
|
+
| `LOOKUP` | O(1) entity lookup by natural key | User references specific entity by name | "Show me Sarah Chen" |
|
|
86
|
+
| `FUZZY` | Trigram text similarity (pg_trgm) | Partial/misspelled names, approximate matches | "Find people named Sara" |
|
|
87
|
+
| `SEARCH` | Semantic vector similarity | Conceptual questions, semantic similarity | "Documents about databases" |
|
|
88
|
+
| `SQL` | Direct table queries with WHERE | Temporal, filtered, or aggregate queries | "Meetings in Q4 2024" |
|
|
89
|
+
| `TRAVERSE` | Recursive graph traversal | Relationships, connections, "what's related" | "What does Sarah manage?" |
|
|
90
|
+
|
|
91
|
+
### Configuration
|
|
92
|
+
|
|
93
|
+
Set the model for REM Query Agent in your environment:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# .env
|
|
97
|
+
LLM__QUERY_AGENT_MODEL=gpt-4o-mini # Fast, cheap model recommended
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If not set, uses `settings.llm.default_model`.
|
|
101
|
+
|
|
102
|
+
### Output Schema
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
class REMQueryOutput(BaseModel):
|
|
106
|
+
query_type: QueryType # Selected query type
|
|
107
|
+
parameters: dict # Query parameters
|
|
108
|
+
confidence: float # 0.0-1.0 confidence score
|
|
109
|
+
reasoning: str | None # Only if confidence < 0.7 or multi-step
|
|
110
|
+
multi_step: list[dict] | None # For complex queries
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Design Philosophy
|
|
114
|
+
|
|
115
|
+
1. **Token Efficiency**: Output is concise by design
|
|
116
|
+
- Reasoning only included when needed (low confidence or multi-step)
|
|
117
|
+
- Minimal fields to reduce generation time and cost
|
|
118
|
+
|
|
119
|
+
2. **PostgreSQL Awareness**: Agent knows the database schema
|
|
120
|
+
- LOOKUP/FUZZY use UNLOGGED KV_STORE (fast cache)
|
|
121
|
+
- SEARCH joins KV_STORE + embeddings_<table>
|
|
122
|
+
- SQL queries primary tables directly
|
|
123
|
+
- TRAVERSE follows graph_edges JSONB field
|
|
124
|
+
|
|
125
|
+
3. **Progressive Complexity**: Prefer simple queries over complex
|
|
126
|
+
- LOOKUP is fastest (O(1))
|
|
127
|
+
- FUZZY uses indexed trigrams
|
|
128
|
+
- SEARCH requires embedding generation
|
|
129
|
+
- SQL scans tables (filtered)
|
|
130
|
+
- TRAVERSE is recursive (most complex)
|
|
131
|
+
|
|
132
|
+
4. **Confidence-Based Execution**: RemService auto-executes if confidence >= 0.7
|
|
133
|
+
- High confidence: Execute immediately
|
|
134
|
+
- Low confidence: Return query + reasoning for review
|
|
135
|
+
|
|
136
|
+
### Testing
|
|
137
|
+
|
|
138
|
+
See `tests/unit/agentic/agents/test_rem_query_agent.py` for unit tests.
|
|
139
|
+
|
|
140
|
+
Tests cover:
|
|
141
|
+
- Schema structure validation
|
|
142
|
+
- Output model creation
|
|
143
|
+
- Confidence validation
|
|
144
|
+
- Multi-step query support
|
|
145
|
+
|
|
146
|
+
Integration tests with actual LLM execution require API keys and are in `tests/integration/`.
|
|
147
|
+
|
|
148
|
+
## Future Agents
|
|
149
|
+
|
|
150
|
+
Additional agents can be added following the same pattern:
|
|
151
|
+
|
|
152
|
+
- **Entity Summarization Agent**: Summarize entity relationships
|
|
153
|
+
- **Query Explanation Agent**: Explain REM query results in natural language
|
|
154
|
+
- **Schema Discovery Agent**: Discover available tables and fields
|
|
155
|
+
- **Data Quality Agent**: Identify data quality issues in entities
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
REM Agents - Specialized agents for REM operations.
|
|
3
|
+
|
|
4
|
+
Most agents are defined as YAML schemas in src/rem/schemas/agents/.
|
|
5
|
+
Use create_agent_from_schema_file() to instantiate agents.
|
|
6
|
+
|
|
7
|
+
The SSE Simulator is a special programmatic "agent" that generates
|
|
8
|
+
scripted SSE events for testing and demonstration - it doesn't use an LLM.
|
|
9
|
+
|
|
10
|
+
Agent Manager provides functions for saving/loading user-created agents.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .sse_simulator import (
|
|
14
|
+
stream_simulator_events,
|
|
15
|
+
stream_minimal_demo,
|
|
16
|
+
stream_error_demo,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from .agent_manager import (
|
|
20
|
+
save_agent,
|
|
21
|
+
get_agent,
|
|
22
|
+
list_agents,
|
|
23
|
+
delete_agent,
|
|
24
|
+
build_agent_spec,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# SSE Simulator (programmatic, no LLM)
|
|
29
|
+
"stream_simulator_events",
|
|
30
|
+
"stream_minimal_demo",
|
|
31
|
+
"stream_error_demo",
|
|
32
|
+
# Agent Manager
|
|
33
|
+
"save_agent",
|
|
34
|
+
"get_agent",
|
|
35
|
+
"list_agents",
|
|
36
|
+
"delete_agent",
|
|
37
|
+
"build_agent_spec",
|
|
38
|
+
]
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Manager - Save, load, and manage user-created agents.
|
|
3
|
+
|
|
4
|
+
This module provides the core functionality for persisting agent schemas
|
|
5
|
+
to the database with user scoping.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from rem.agentic.agents.agent_manager import save_agent, get_agent, list_agents
|
|
9
|
+
|
|
10
|
+
# Save an agent
|
|
11
|
+
result = await save_agent(
|
|
12
|
+
name="my-assistant",
|
|
13
|
+
description="You are a helpful assistant.",
|
|
14
|
+
user_id="user-123"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Get an agent
|
|
18
|
+
agent = await get_agent("my-assistant", user_id="user-123")
|
|
19
|
+
|
|
20
|
+
# List user's agents
|
|
21
|
+
agents = await list_agents(user_id="user-123")
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from typing import Any
|
|
25
|
+
from loguru import logger
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
DEFAULT_TOOLS = ["search_rem", "register_metadata"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_agent_spec(
|
|
32
|
+
name: str,
|
|
33
|
+
description: str,
|
|
34
|
+
properties: dict[str, Any] | None = None,
|
|
35
|
+
required: list[str] | None = None,
|
|
36
|
+
tools: list[str] | None = None,
|
|
37
|
+
tags: list[str] | None = None,
|
|
38
|
+
version: str = "1.0.0",
|
|
39
|
+
) -> dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Build a valid agent schema spec.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
name: Agent name in kebab-case
|
|
45
|
+
description: System prompt for the agent
|
|
46
|
+
properties: Output schema properties
|
|
47
|
+
required: Required property names
|
|
48
|
+
tools: Tool names (defaults to search_rem, register_metadata)
|
|
49
|
+
tags: Categorization tags
|
|
50
|
+
version: Semantic version
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Valid agent schema spec dict
|
|
54
|
+
"""
|
|
55
|
+
# Default properties
|
|
56
|
+
if properties is None:
|
|
57
|
+
properties = {
|
|
58
|
+
"answer": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Natural language response to the user"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Default required
|
|
65
|
+
if required is None:
|
|
66
|
+
required = ["answer"]
|
|
67
|
+
|
|
68
|
+
# Default tools
|
|
69
|
+
if tools is None:
|
|
70
|
+
tools = DEFAULT_TOOLS.copy()
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"description": description,
|
|
75
|
+
"properties": properties,
|
|
76
|
+
"required": required,
|
|
77
|
+
"json_schema_extra": {
|
|
78
|
+
"kind": "agent",
|
|
79
|
+
"name": name,
|
|
80
|
+
"version": version,
|
|
81
|
+
"tags": tags or [],
|
|
82
|
+
"tools": [{"name": t, "description": f"Tool: {t}"} for t in tools],
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def save_agent(
|
|
88
|
+
name: str,
|
|
89
|
+
description: str,
|
|
90
|
+
user_id: str,
|
|
91
|
+
properties: dict[str, Any] | None = None,
|
|
92
|
+
required: list[str] | None = None,
|
|
93
|
+
tools: list[str] | None = None,
|
|
94
|
+
tags: list[str] | None = None,
|
|
95
|
+
version: str = "1.0.0",
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
"""
|
|
98
|
+
Save an agent schema to the database.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
name: Agent name in kebab-case (e.g., "code-reviewer")
|
|
102
|
+
description: The agent's system prompt
|
|
103
|
+
user_id: User identifier for scoping
|
|
104
|
+
properties: Output schema properties
|
|
105
|
+
required: Required property names
|
|
106
|
+
tools: Tool names
|
|
107
|
+
tags: Categorization tags
|
|
108
|
+
version: Semantic version
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dict with status, agent_name, version, message
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
RuntimeError: If database is not available
|
|
115
|
+
"""
|
|
116
|
+
from rem.models.entities import Schema
|
|
117
|
+
from rem.services.postgres import get_postgres_service
|
|
118
|
+
|
|
119
|
+
# Build the spec
|
|
120
|
+
spec = build_agent_spec(
|
|
121
|
+
name=name,
|
|
122
|
+
description=description,
|
|
123
|
+
properties=properties,
|
|
124
|
+
required=required,
|
|
125
|
+
tools=tools,
|
|
126
|
+
tags=tags,
|
|
127
|
+
version=version,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Create Schema entity (user-scoped)
|
|
131
|
+
# Note: tenant_id defaults to "default" for anonymous users
|
|
132
|
+
schema_entity = Schema(
|
|
133
|
+
tenant_id=user_id or "default",
|
|
134
|
+
user_id=user_id,
|
|
135
|
+
name=name,
|
|
136
|
+
spec=spec,
|
|
137
|
+
category="agent",
|
|
138
|
+
metadata={
|
|
139
|
+
"version": version,
|
|
140
|
+
"tags": tags or [],
|
|
141
|
+
"created_via": "agent_manager",
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Save to database
|
|
146
|
+
postgres = get_postgres_service()
|
|
147
|
+
if not postgres:
|
|
148
|
+
raise RuntimeError("Database not available")
|
|
149
|
+
|
|
150
|
+
await postgres.connect()
|
|
151
|
+
try:
|
|
152
|
+
await postgres.batch_upsert(
|
|
153
|
+
records=[schema_entity],
|
|
154
|
+
model=Schema,
|
|
155
|
+
table_name="schemas",
|
|
156
|
+
entity_key_field="name",
|
|
157
|
+
generate_embeddings=False,
|
|
158
|
+
)
|
|
159
|
+
logger.info(f"✅ Agent saved: {name} (user={user_id}, version={version})")
|
|
160
|
+
finally:
|
|
161
|
+
await postgres.disconnect()
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"status": "success",
|
|
165
|
+
"agent_name": name,
|
|
166
|
+
"version": version,
|
|
167
|
+
"message": f"Agent '{name}' saved successfully.",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def get_agent(
|
|
172
|
+
name: str,
|
|
173
|
+
user_id: str,
|
|
174
|
+
) -> dict[str, Any] | None:
|
|
175
|
+
"""
|
|
176
|
+
Get an agent schema by name.
|
|
177
|
+
|
|
178
|
+
Checks user's schemas first, then falls back to system schemas.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
name: Agent name
|
|
182
|
+
user_id: User identifier
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Agent spec dict if found, None otherwise
|
|
186
|
+
"""
|
|
187
|
+
from rem.services.postgres import get_postgres_service
|
|
188
|
+
|
|
189
|
+
postgres = get_postgres_service()
|
|
190
|
+
if not postgres:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
await postgres.connect()
|
|
194
|
+
try:
|
|
195
|
+
query = """
|
|
196
|
+
SELECT spec FROM schemas
|
|
197
|
+
WHERE LOWER(name) = LOWER($1)
|
|
198
|
+
AND category = 'agent'
|
|
199
|
+
AND (user_id = $2 OR user_id IS NULL OR tenant_id = 'system')
|
|
200
|
+
ORDER BY CASE WHEN user_id = $2 THEN 0 ELSE 1 END
|
|
201
|
+
LIMIT 1
|
|
202
|
+
"""
|
|
203
|
+
row = await postgres.fetchrow(query, name, user_id)
|
|
204
|
+
if row:
|
|
205
|
+
return row["spec"]
|
|
206
|
+
return None
|
|
207
|
+
finally:
|
|
208
|
+
await postgres.disconnect()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
async def list_agents(
|
|
212
|
+
user_id: str,
|
|
213
|
+
include_system: bool = True,
|
|
214
|
+
) -> list[dict[str, Any]]:
|
|
215
|
+
"""
|
|
216
|
+
List available agents for a user.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
user_id: User identifier
|
|
220
|
+
include_system: Include system agents
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
List of agent metadata dicts
|
|
224
|
+
"""
|
|
225
|
+
from rem.services.postgres import get_postgres_service
|
|
226
|
+
|
|
227
|
+
postgres = get_postgres_service()
|
|
228
|
+
if not postgres:
|
|
229
|
+
return []
|
|
230
|
+
|
|
231
|
+
await postgres.connect()
|
|
232
|
+
try:
|
|
233
|
+
if include_system:
|
|
234
|
+
query = """
|
|
235
|
+
SELECT name, metadata, user_id, tenant_id
|
|
236
|
+
FROM schemas
|
|
237
|
+
WHERE category = 'agent'
|
|
238
|
+
AND (user_id = $1 OR user_id IS NULL OR tenant_id = 'system')
|
|
239
|
+
ORDER BY name
|
|
240
|
+
"""
|
|
241
|
+
rows = await postgres.fetch(query, user_id)
|
|
242
|
+
else:
|
|
243
|
+
query = """
|
|
244
|
+
SELECT name, metadata, user_id, tenant_id
|
|
245
|
+
FROM schemas
|
|
246
|
+
WHERE category = 'agent'
|
|
247
|
+
AND user_id = $1
|
|
248
|
+
ORDER BY name
|
|
249
|
+
"""
|
|
250
|
+
rows = await postgres.fetch(query, user_id)
|
|
251
|
+
|
|
252
|
+
return [
|
|
253
|
+
{
|
|
254
|
+
"name": row["name"],
|
|
255
|
+
"version": row["metadata"].get("version", "1.0.0") if row["metadata"] else "1.0.0",
|
|
256
|
+
"tags": row["metadata"].get("tags", []) if row["metadata"] else [],
|
|
257
|
+
"is_system": row["tenant_id"] == "system" or row["user_id"] is None,
|
|
258
|
+
}
|
|
259
|
+
for row in rows
|
|
260
|
+
]
|
|
261
|
+
finally:
|
|
262
|
+
await postgres.disconnect()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
async def delete_agent(
|
|
266
|
+
name: str,
|
|
267
|
+
user_id: str,
|
|
268
|
+
) -> dict[str, Any]:
|
|
269
|
+
"""
|
|
270
|
+
Delete a user's agent.
|
|
271
|
+
|
|
272
|
+
Only allows deleting user-owned agents, not system agents.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
name: Agent name
|
|
276
|
+
user_id: User identifier
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Dict with status and message
|
|
280
|
+
"""
|
|
281
|
+
from rem.services.postgres import get_postgres_service
|
|
282
|
+
|
|
283
|
+
postgres = get_postgres_service()
|
|
284
|
+
if not postgres:
|
|
285
|
+
raise RuntimeError("Database not available")
|
|
286
|
+
|
|
287
|
+
await postgres.connect()
|
|
288
|
+
try:
|
|
289
|
+
# Only delete user's own agents
|
|
290
|
+
query = """
|
|
291
|
+
DELETE FROM schemas
|
|
292
|
+
WHERE LOWER(name) = LOWER($1)
|
|
293
|
+
AND category = 'agent'
|
|
294
|
+
AND user_id = $2
|
|
295
|
+
RETURNING name
|
|
296
|
+
"""
|
|
297
|
+
row = await postgres.fetchrow(query, name, user_id)
|
|
298
|
+
|
|
299
|
+
if row:
|
|
300
|
+
logger.info(f"🗑️ Agent deleted: {name} (user={user_id})")
|
|
301
|
+
return {
|
|
302
|
+
"status": "success",
|
|
303
|
+
"message": f"Agent '{name}' deleted.",
|
|
304
|
+
}
|
|
305
|
+
else:
|
|
306
|
+
return {
|
|
307
|
+
"status": "error",
|
|
308
|
+
"message": f"Agent '{name}' not found or not owned by you.",
|
|
309
|
+
}
|
|
310
|
+
finally:
|
|
311
|
+
await postgres.disconnect()
|