remdb 0.3.7__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/__init__.py +2 -0
- rem/agentic/README.md +650 -0
- rem/agentic/__init__.py +39 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +8 -0
- rem/agentic/context.py +148 -0
- rem/agentic/context_builder.py +329 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +107 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +151 -0
- rem/agentic/providers/phoenix.py +674 -0
- rem/agentic/providers/pydantic_ai.py +572 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +396 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +231 -0
- rem/api/README.md +420 -0
- rem/api/main.py +324 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +536 -0
- rem/api/mcp_router/server.py +213 -0
- rem/api/mcp_router/tools.py +584 -0
- rem/api/routers/auth.py +229 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/completions.py +281 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +124 -0
- rem/api/routers/chat/streaming.py +185 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +26 -0
- rem/auth/middleware.py +100 -0
- rem/auth/providers/__init__.py +13 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +455 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +126 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +566 -0
- rem/cli/commands/configure.py +497 -0
- rem/cli/commands/db.py +493 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1302 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +245 -0
- rem/cli/commands/schema.py +183 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +96 -0
- rem/config.py +237 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +64 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +628 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +243 -0
- rem/models/entities/__init__.py +43 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +35 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +191 -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/user.py +85 -0
- rem/py.typed +0 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -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 +128 -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 +16 -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 +801 -0
- rem/services/content/service.py +676 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +336 -0
- rem/services/dreaming/moment_service.py +264 -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/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +120 -0
- rem/services/embeddings/worker.py +421 -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 +686 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +575 -0
- rem/services/postgres/__init__.py +23 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +232 -0
- rem/services/postgres/register_type.py +352 -0
- rem/services/postgres/repository.py +337 -0
- rem/services/postgres/schema_generator.py +379 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +354 -0
- rem/services/rem/README.md +304 -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 +145 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +527 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +6 -0
- rem/services/session/compression.py +360 -0
- rem/services/session/reload.py +77 -0
- rem/settings.py +1235 -0
- rem/sql/002_install_models.sql +1068 -0
- rem/sql/background_indexes.sql +42 -0
- rem/sql/install_models.sql +1038 -0
- rem/sql/migrations/001_install.sql +503 -0
- rem/sql/migrations/002_install_models.sql +1202 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +583 -0
- rem/utils/__init__.py +43 -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/dict_utils.py +98 -0
- rem/utils/embeddings.py +423 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/markdown.py +16 -0
- rem/utils/model_helpers.py +236 -0
- rem/utils/schema_loader.py +336 -0
- rem/utils/sql_types.py +348 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +330 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +5 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- remdb-0.3.7.dist-info/METADATA +1473 -0
- remdb-0.3.7.dist-info/RECORD +187 -0
- remdb-0.3.7.dist-info/WHEEL +4 -0
- remdb-0.3.7.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tools for REM operations.
|
|
3
|
+
|
|
4
|
+
Tools are functions that LLMs can call to interact with the REM system.
|
|
5
|
+
Each tool is decorated with @mcp.tool() and registered with the FastMCP server.
|
|
6
|
+
|
|
7
|
+
Design Pattern:
|
|
8
|
+
- Tools receive parameters from LLM
|
|
9
|
+
- Tools delegate to RemService or ContentService
|
|
10
|
+
- Tools return structured results
|
|
11
|
+
- Tools handle errors gracefully with informative messages
|
|
12
|
+
|
|
13
|
+
Available Tools:
|
|
14
|
+
- search_rem: Execute REM queries (LOOKUP, FUZZY, SEARCH, SQL, TRAVERSE)
|
|
15
|
+
- ask_rem_agent: Natural language to REM query conversion via agent
|
|
16
|
+
- ingest_into_rem: Full file ingestion pipeline (read + store + parse + chunk)
|
|
17
|
+
- read_resource: Access MCP resources (for Claude Desktop compatibility)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from functools import wraps
|
|
21
|
+
from typing import Any, Callable, Literal, cast
|
|
22
|
+
|
|
23
|
+
from loguru import logger
|
|
24
|
+
|
|
25
|
+
from ...agentic.context import AgentContext
|
|
26
|
+
from ...models.core import (
|
|
27
|
+
FuzzyParameters,
|
|
28
|
+
LookupParameters,
|
|
29
|
+
QueryType,
|
|
30
|
+
RemQuery,
|
|
31
|
+
SearchParameters,
|
|
32
|
+
SQLParameters,
|
|
33
|
+
TraverseParameters,
|
|
34
|
+
)
|
|
35
|
+
from ...services.postgres import PostgresService
|
|
36
|
+
from ...services.rem import RemService
|
|
37
|
+
from ...settings import settings
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Service cache for FastAPI lifespan initialization
|
|
41
|
+
_service_cache: dict[str, Any] = {}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def init_services(postgres_service: PostgresService, rem_service: RemService):
|
|
45
|
+
"""
|
|
46
|
+
Initialize service instances for MCP tools.
|
|
47
|
+
|
|
48
|
+
Called during FastAPI lifespan startup.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
postgres_service: PostgresService instance
|
|
52
|
+
rem_service: RemService instance
|
|
53
|
+
"""
|
|
54
|
+
_service_cache["postgres"] = postgres_service
|
|
55
|
+
_service_cache["rem"] = rem_service
|
|
56
|
+
logger.info("MCP tools initialized with service instances")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def get_rem_service() -> RemService:
|
|
60
|
+
"""
|
|
61
|
+
Get or create RemService instance (lazy initialization).
|
|
62
|
+
|
|
63
|
+
Returns cached instance if available, otherwise creates new one.
|
|
64
|
+
Thread-safe for async usage.
|
|
65
|
+
"""
|
|
66
|
+
if "rem" in _service_cache:
|
|
67
|
+
return cast(RemService, _service_cache["rem"])
|
|
68
|
+
|
|
69
|
+
# Lazy initialization for in-process/CLI usage
|
|
70
|
+
from ...services.postgres import get_postgres_service
|
|
71
|
+
|
|
72
|
+
postgres_service = get_postgres_service()
|
|
73
|
+
if not postgres_service:
|
|
74
|
+
raise RuntimeError("PostgreSQL is disabled. Cannot use REM service.")
|
|
75
|
+
|
|
76
|
+
await postgres_service.connect()
|
|
77
|
+
rem_service = RemService(postgres_service=postgres_service)
|
|
78
|
+
|
|
79
|
+
_service_cache["postgres"] = postgres_service
|
|
80
|
+
_service_cache["rem"] = rem_service
|
|
81
|
+
|
|
82
|
+
logger.info("MCP tools: lazy initialized services")
|
|
83
|
+
return rem_service
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def mcp_tool_error_handler(func: Callable) -> Callable:
|
|
87
|
+
"""
|
|
88
|
+
Decorator for consistent MCP tool error handling.
|
|
89
|
+
|
|
90
|
+
Wraps tool functions to:
|
|
91
|
+
- Log errors with full context
|
|
92
|
+
- Return standardized error responses
|
|
93
|
+
- Prevent exceptions from bubbling to LLM
|
|
94
|
+
|
|
95
|
+
Usage:
|
|
96
|
+
@mcp_tool_error_handler
|
|
97
|
+
async def my_tool(...) -> dict[str, Any]:
|
|
98
|
+
# Pure business logic - no try/except needed
|
|
99
|
+
result = await service.do_work()
|
|
100
|
+
return {"data": result}
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
{"status": "success", **result} on success
|
|
104
|
+
{"status": "error", "error": str(e)} on failure
|
|
105
|
+
"""
|
|
106
|
+
@wraps(func)
|
|
107
|
+
async def wrapper(*args, **kwargs) -> dict[str, Any]:
|
|
108
|
+
try:
|
|
109
|
+
result = await func(*args, **kwargs)
|
|
110
|
+
# If result already has status, return as-is
|
|
111
|
+
if isinstance(result, dict) and "status" in result:
|
|
112
|
+
return result
|
|
113
|
+
# Otherwise wrap in success response
|
|
114
|
+
return {"status": "success", **result}
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"{func.__name__} failed: {e}", exc_info=True)
|
|
117
|
+
return {
|
|
118
|
+
"status": "error",
|
|
119
|
+
"error": str(e),
|
|
120
|
+
"tool": func.__name__,
|
|
121
|
+
}
|
|
122
|
+
return wrapper
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@mcp_tool_error_handler
|
|
126
|
+
async def search_rem(
|
|
127
|
+
query_type: Literal["lookup", "fuzzy", "search", "sql", "traverse"],
|
|
128
|
+
# LOOKUP parameters
|
|
129
|
+
entity_key: str | None = None,
|
|
130
|
+
# FUZZY parameters
|
|
131
|
+
query_text: str | None = None,
|
|
132
|
+
threshold: float = 0.7,
|
|
133
|
+
# SEARCH parameters
|
|
134
|
+
table: str | None = None,
|
|
135
|
+
limit: int = 20,
|
|
136
|
+
# SQL parameters
|
|
137
|
+
sql_query: str | None = None,
|
|
138
|
+
# TRAVERSE parameters
|
|
139
|
+
initial_query: str | None = None,
|
|
140
|
+
edge_types: list[str] | None = None,
|
|
141
|
+
depth: int = 1,
|
|
142
|
+
# Optional context override (defaults to authenticated user)
|
|
143
|
+
user_id: str | None = None,
|
|
144
|
+
) -> dict[str, Any]:
|
|
145
|
+
"""
|
|
146
|
+
Execute REM queries for entity lookup, semantic search, and graph traversal.
|
|
147
|
+
|
|
148
|
+
REM supports multiple query types for different retrieval patterns:
|
|
149
|
+
|
|
150
|
+
**LOOKUP** - O(1) entity resolution by natural language key:
|
|
151
|
+
- Fast exact match across all tables
|
|
152
|
+
- Uses indexed label_vector for instant retrieval
|
|
153
|
+
- Example: LOOKUP "Sarah Chen" returns all entities named "Sarah Chen"
|
|
154
|
+
|
|
155
|
+
**FUZZY** - Fuzzy text matching with similarity threshold:
|
|
156
|
+
- Finds partial matches and typos
|
|
157
|
+
- Example: FUZZY "sara" threshold=0.7 finds "Sarah Chen", "Sara Martinez"
|
|
158
|
+
|
|
159
|
+
**SEARCH** - Semantic vector search (table-specific):
|
|
160
|
+
- Finds conceptually similar entities
|
|
161
|
+
- Example: SEARCH "database migration" table=resources returns related documents
|
|
162
|
+
|
|
163
|
+
**SQL** - Direct SQL queries for structured data:
|
|
164
|
+
- Full PostgreSQL query power (scoped to table)
|
|
165
|
+
- Example: SQL "role = 'engineer'" (WHERE clause only)
|
|
166
|
+
|
|
167
|
+
**TRAVERSE** - Graph traversal following relationships:
|
|
168
|
+
- Explores entity neighborhood via graph edges
|
|
169
|
+
- Supports depth control and edge type filtering
|
|
170
|
+
- Example: TRAVERSE "Sarah Chen" edge_types=["manages", "reports_to"] depth=2
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
query_type: Type of query (lookup, fuzzy, search, sql, traverse)
|
|
174
|
+
entity_key: Entity key for LOOKUP (e.g., "Sarah Chen")
|
|
175
|
+
query_text: Search text for FUZZY or SEARCH
|
|
176
|
+
threshold: Similarity threshold for FUZZY (0.0-1.0)
|
|
177
|
+
table: Target table for SEARCH (resources, moments, users, etc.)
|
|
178
|
+
limit: Max results for SEARCH
|
|
179
|
+
sql_query: SQL WHERE clause for SQL type (e.g. "id = '123'")
|
|
180
|
+
initial_query: Starting entity for TRAVERSE
|
|
181
|
+
edge_types: Edge types to follow for TRAVERSE (e.g., ["manages", "reports_to"])
|
|
182
|
+
depth: Traversal depth for TRAVERSE (0=plan only, 1-5=actual traversal)
|
|
183
|
+
user_id: Optional user identifier (defaults to authenticated user or "default")
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Dict with query results, metadata, and execution info
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
# Lookup entity (uses authenticated user context)
|
|
190
|
+
search_rem(
|
|
191
|
+
query_type="lookup",
|
|
192
|
+
entity_key="Sarah Chen"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Semantic search
|
|
196
|
+
search_rem(
|
|
197
|
+
query_type="search",
|
|
198
|
+
query_text="database migration",
|
|
199
|
+
table="resources",
|
|
200
|
+
limit=10
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# SQL query (WHERE clause only)
|
|
204
|
+
search_rem(
|
|
205
|
+
query_type="sql",
|
|
206
|
+
table="resources",
|
|
207
|
+
sql_query="category = 'document'"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Graph traversal
|
|
211
|
+
search_rem(
|
|
212
|
+
query_type="traverse",
|
|
213
|
+
initial_query="Sarah Chen",
|
|
214
|
+
edge_types=["manages", "reports_to"],
|
|
215
|
+
depth=2
|
|
216
|
+
)
|
|
217
|
+
"""
|
|
218
|
+
# Get RemService instance (lazy initialization)
|
|
219
|
+
rem_service = await get_rem_service()
|
|
220
|
+
|
|
221
|
+
# Get user_id from context if not provided
|
|
222
|
+
# TODO: Extract from authenticated session context when auth is enabled
|
|
223
|
+
user_id = AgentContext.get_user_id_or_default(user_id, source="search_rem")
|
|
224
|
+
|
|
225
|
+
# Normalize query_type to lowercase for case-insensitive REM dialect
|
|
226
|
+
query_type = cast(Literal["lookup", "fuzzy", "search", "sql", "traverse"], query_type.lower())
|
|
227
|
+
|
|
228
|
+
# Build RemQuery based on query_type
|
|
229
|
+
if query_type == "lookup":
|
|
230
|
+
if not entity_key:
|
|
231
|
+
return {"status": "error", "error": "entity_key required for LOOKUP"}
|
|
232
|
+
|
|
233
|
+
query = RemQuery(
|
|
234
|
+
query_type=QueryType.LOOKUP,
|
|
235
|
+
parameters=LookupParameters(
|
|
236
|
+
key=entity_key,
|
|
237
|
+
user_id=user_id,
|
|
238
|
+
),
|
|
239
|
+
user_id=user_id,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
elif query_type == "fuzzy":
|
|
243
|
+
if not query_text:
|
|
244
|
+
return {"status": "error", "error": "query_text required for FUZZY"}
|
|
245
|
+
|
|
246
|
+
query = RemQuery(
|
|
247
|
+
query_type=QueryType.FUZZY,
|
|
248
|
+
parameters=FuzzyParameters(
|
|
249
|
+
query_text=query_text,
|
|
250
|
+
threshold=threshold,
|
|
251
|
+
limit=limit, # Limit was missing in original logic but likely intended
|
|
252
|
+
),
|
|
253
|
+
user_id=user_id,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
elif query_type == "search":
|
|
257
|
+
if not query_text:
|
|
258
|
+
return {"status": "error", "error": "query_text required for SEARCH"}
|
|
259
|
+
if not table:
|
|
260
|
+
return {"status": "error", "error": "table required for SEARCH"}
|
|
261
|
+
|
|
262
|
+
query = RemQuery(
|
|
263
|
+
query_type=QueryType.SEARCH,
|
|
264
|
+
parameters=SearchParameters(
|
|
265
|
+
query_text=query_text,
|
|
266
|
+
table_name=table,
|
|
267
|
+
limit=limit,
|
|
268
|
+
),
|
|
269
|
+
user_id=user_id,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
elif query_type == "sql":
|
|
273
|
+
if not sql_query:
|
|
274
|
+
return {"status": "error", "error": "sql_query required for SQL"}
|
|
275
|
+
|
|
276
|
+
# SQLParameters requires table_name. If not provided, we cannot execute.
|
|
277
|
+
# Assuming sql_query is just the WHERE clause based on RemService implementation,
|
|
278
|
+
# OR if table is provided we use it.
|
|
279
|
+
if not table:
|
|
280
|
+
return {"status": "error", "error": "table required for SQL queries (parameter: table)"}
|
|
281
|
+
|
|
282
|
+
query = RemQuery(
|
|
283
|
+
query_type=QueryType.SQL,
|
|
284
|
+
parameters=SQLParameters(
|
|
285
|
+
table_name=table,
|
|
286
|
+
where_clause=sql_query,
|
|
287
|
+
limit=limit,
|
|
288
|
+
),
|
|
289
|
+
user_id=user_id,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
elif query_type == "traverse":
|
|
293
|
+
if not initial_query:
|
|
294
|
+
return {
|
|
295
|
+
"status": "error",
|
|
296
|
+
"error": "initial_query required for TRAVERSE",
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
query = RemQuery(
|
|
300
|
+
query_type=QueryType.TRAVERSE,
|
|
301
|
+
parameters=TraverseParameters(
|
|
302
|
+
initial_query=initial_query,
|
|
303
|
+
edge_types=edge_types or [],
|
|
304
|
+
max_depth=depth,
|
|
305
|
+
),
|
|
306
|
+
user_id=user_id,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
else:
|
|
310
|
+
return {"status": "error", "error": f"Unknown query_type: {query_type}"}
|
|
311
|
+
|
|
312
|
+
# Execute query (errors handled by decorator)
|
|
313
|
+
logger.info(f"Executing REM query: {query_type} for user {user_id}")
|
|
314
|
+
result = await rem_service.execute_query(query)
|
|
315
|
+
|
|
316
|
+
logger.info(f"Query completed successfully: {query_type}")
|
|
317
|
+
return {
|
|
318
|
+
"query_type": query_type,
|
|
319
|
+
"results": result,
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@mcp_tool_error_handler
|
|
324
|
+
async def ask_rem_agent(
|
|
325
|
+
query: str,
|
|
326
|
+
agent_schema: str = "ask_rem",
|
|
327
|
+
agent_version: str | None = None,
|
|
328
|
+
user_id: str | None = None,
|
|
329
|
+
) -> dict[str, Any]:
|
|
330
|
+
"""
|
|
331
|
+
Ask REM using natural language via agent-driven query conversion.
|
|
332
|
+
|
|
333
|
+
This tool converts natural language questions into optimized REM queries
|
|
334
|
+
using an agent that understands the REM query language and schema.
|
|
335
|
+
|
|
336
|
+
The agent can perform multi-turn reasoning and iterated retrieval:
|
|
337
|
+
1. Initial exploration (LOOKUP/FUZZY to find entities)
|
|
338
|
+
2. Semantic search (SEARCH for related content)
|
|
339
|
+
3. Graph traversal (TRAVERSE to explore relationships)
|
|
340
|
+
4. Synthesis (combine results into final answer)
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
query: Natural language question or task
|
|
344
|
+
agent_schema: Agent schema name (default: "ask_rem")
|
|
345
|
+
agent_version: Optional agent version (default: latest)
|
|
346
|
+
user_id: Optional user identifier (defaults to authenticated user or "default")
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Dict with:
|
|
350
|
+
- status: "success" or "error"
|
|
351
|
+
- response: Agent's natural language response
|
|
352
|
+
- query_output: Structured query results (if available)
|
|
353
|
+
- queries_executed: List of REM queries executed
|
|
354
|
+
- metadata: Agent execution metadata
|
|
355
|
+
|
|
356
|
+
Examples:
|
|
357
|
+
# Simple question (uses authenticated user context)
|
|
358
|
+
ask_rem_agent(
|
|
359
|
+
query="Who is Sarah Chen?"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Complex multi-step question
|
|
363
|
+
ask_rem_agent(
|
|
364
|
+
query="What are the key findings from last week's sprint retrospective?"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Graph exploration
|
|
368
|
+
ask_rem_agent(
|
|
369
|
+
query="Show me Sarah's reporting chain and their recent projects"
|
|
370
|
+
)
|
|
371
|
+
"""
|
|
372
|
+
# Get user_id from context if not provided
|
|
373
|
+
# TODO: Extract from authenticated session context when auth is enabled
|
|
374
|
+
user_id = AgentContext.get_user_id_or_default(user_id, source="ask_rem_agent")
|
|
375
|
+
|
|
376
|
+
from ...agentic import create_agent
|
|
377
|
+
from ...utils.schema_loader import load_agent_schema
|
|
378
|
+
|
|
379
|
+
# Create agent context
|
|
380
|
+
context = AgentContext(
|
|
381
|
+
user_id=user_id,
|
|
382
|
+
tenant_id=user_id, # Set tenant_id to user_id for backward compat
|
|
383
|
+
default_model=settings.llm.default_model,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Load agent schema
|
|
387
|
+
try:
|
|
388
|
+
schema = load_agent_schema(agent_schema)
|
|
389
|
+
except FileNotFoundError:
|
|
390
|
+
return {
|
|
391
|
+
"status": "error",
|
|
392
|
+
"error": f"Agent schema not found: {agent_schema}",
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
# Create agent
|
|
396
|
+
agent_runtime = await create_agent(
|
|
397
|
+
context=context,
|
|
398
|
+
agent_schema_override=schema,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Run agent (errors handled by decorator)
|
|
402
|
+
logger.info(f"Running ask_rem agent for query: {query[:100]}...")
|
|
403
|
+
result = await agent_runtime.run(query)
|
|
404
|
+
|
|
405
|
+
# Extract output
|
|
406
|
+
from rem.agentic.serialization import serialize_agent_result
|
|
407
|
+
query_output = serialize_agent_result(result.output)
|
|
408
|
+
|
|
409
|
+
logger.info("Agent execution completed successfully")
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
"response": str(result.output),
|
|
413
|
+
"query_output": query_output,
|
|
414
|
+
"natural_query": query,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@mcp_tool_error_handler
|
|
419
|
+
async def ingest_into_rem(
|
|
420
|
+
file_uri: str,
|
|
421
|
+
category: str | None = None,
|
|
422
|
+
tags: list[str] | None = None,
|
|
423
|
+
is_local_server: bool = False,
|
|
424
|
+
user_id: str | None = None,
|
|
425
|
+
) -> dict[str, Any]:
|
|
426
|
+
"""
|
|
427
|
+
Ingest file into REM, creating searchable resources and embeddings.
|
|
428
|
+
|
|
429
|
+
This tool provides the complete file ingestion pipeline:
|
|
430
|
+
1. **Read**: File from local/S3/HTTP
|
|
431
|
+
2. **Store**: To user-scoped internal storage
|
|
432
|
+
3. **Parse**: Extract content, metadata, tables, images
|
|
433
|
+
4. **Chunk**: Semantic chunking for embeddings
|
|
434
|
+
5. **Embed**: Create Resource chunks with vector embeddings
|
|
435
|
+
|
|
436
|
+
Supported file types:
|
|
437
|
+
- Documents: PDF, DOCX, TXT, Markdown
|
|
438
|
+
- Code: Python, JavaScript, TypeScript, etc.
|
|
439
|
+
- Data: CSV, JSON, YAML
|
|
440
|
+
- Audio: WAV, MP3 (transcription)
|
|
441
|
+
|
|
442
|
+
**Security**: Remote MCP servers cannot read local files. Only local/stdio
|
|
443
|
+
MCP servers can access local filesystem paths.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
file_uri: File location (local path, s3:// URI, or http(s):// URL)
|
|
447
|
+
category: Optional category (document, code, audio, etc.)
|
|
448
|
+
tags: Optional tags for file
|
|
449
|
+
is_local_server: True if running as local/stdio MCP server
|
|
450
|
+
user_id: Optional user identifier (defaults to authenticated user or "default")
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Dict with:
|
|
454
|
+
- status: "success" or "error"
|
|
455
|
+
- file_id: Created file UUID
|
|
456
|
+
- file_name: Original filename
|
|
457
|
+
- storage_uri: Internal storage URI
|
|
458
|
+
- processing_status: "completed" or "failed"
|
|
459
|
+
- resources_created: Number of Resource chunks created
|
|
460
|
+
- content: Parsed file content (markdown format) if completed
|
|
461
|
+
- message: Human-readable status message
|
|
462
|
+
|
|
463
|
+
Examples:
|
|
464
|
+
# Ingest local file (local server only, uses authenticated user context)
|
|
465
|
+
ingest_into_rem(
|
|
466
|
+
file_uri="/Users/me/contract.pdf",
|
|
467
|
+
category="legal",
|
|
468
|
+
is_local_server=True
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
# Ingest from S3
|
|
472
|
+
ingest_into_rem(
|
|
473
|
+
file_uri="s3://bucket/docs/report.pdf"
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# Ingest from HTTP
|
|
477
|
+
ingest_into_rem(
|
|
478
|
+
file_uri="https://example.com/whitepaper.pdf",
|
|
479
|
+
tags=["research", "whitepaper"]
|
|
480
|
+
)
|
|
481
|
+
"""
|
|
482
|
+
from ...services.content import ContentService
|
|
483
|
+
|
|
484
|
+
# Get user_id from context if not provided
|
|
485
|
+
# TODO: Extract from authenticated session context when auth is enabled
|
|
486
|
+
user_id = AgentContext.get_user_id_or_default(user_id, source="ingest_into_rem")
|
|
487
|
+
|
|
488
|
+
# Delegate to ContentService for centralized ingestion (errors handled by decorator)
|
|
489
|
+
content_service = ContentService()
|
|
490
|
+
result = await content_service.ingest_file(
|
|
491
|
+
file_uri=file_uri,
|
|
492
|
+
user_id=user_id,
|
|
493
|
+
category=category,
|
|
494
|
+
tags=tags,
|
|
495
|
+
is_local_server=is_local_server,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
logger.info(
|
|
499
|
+
f"MCP ingestion complete: {result['file_name']} "
|
|
500
|
+
f"(status: {result['processing_status']}, "
|
|
501
|
+
f"resources: {result['resources_created']})"
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return result
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@mcp_tool_error_handler
|
|
508
|
+
async def read_resource(uri: str) -> dict[str, Any]:
|
|
509
|
+
"""
|
|
510
|
+
Read an MCP resource by URI.
|
|
511
|
+
|
|
512
|
+
This tool provides automatic access to MCP resources in Claude Desktop.
|
|
513
|
+
Resources contain authoritative, up-to-date reference data.
|
|
514
|
+
|
|
515
|
+
**IMPORTANT**: This tool enables Claude Desktop to automatically access
|
|
516
|
+
resources based on query relevance. While FastMCP correctly exposes resources
|
|
517
|
+
via standard MCP resource endpoints, Claude Desktop currently requires manual
|
|
518
|
+
resource attachment. This tool bridges that gap by exposing resource access
|
|
519
|
+
as a tool, which Claude Desktop WILL automatically invoke.
|
|
520
|
+
|
|
521
|
+
**Available Resources:**
|
|
522
|
+
|
|
523
|
+
Agent Schemas:
|
|
524
|
+
• rem://schemas - List all agent schemas
|
|
525
|
+
• rem://schema/{name} - Get specific schema definition
|
|
526
|
+
• rem://schema/{name}/{version} - Get specific version
|
|
527
|
+
|
|
528
|
+
System Status:
|
|
529
|
+
• rem://status - System health and statistics
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
uri: Resource URI (e.g., "rem://schemas", "rem://schema/ask_rem")
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Dict with:
|
|
536
|
+
- status: "success" or "error"
|
|
537
|
+
- uri: Original URI
|
|
538
|
+
- data: Resource data (format depends on resource type)
|
|
539
|
+
|
|
540
|
+
Examples:
|
|
541
|
+
# List all schemas
|
|
542
|
+
read_resource(uri="rem://schemas")
|
|
543
|
+
|
|
544
|
+
# Get specific schema
|
|
545
|
+
read_resource(uri="rem://schema/ask_rem")
|
|
546
|
+
|
|
547
|
+
# Get schema version
|
|
548
|
+
read_resource(uri="rem://schema/ask_rem/v1.0.0")
|
|
549
|
+
|
|
550
|
+
# Check system status
|
|
551
|
+
read_resource(uri="rem://status")
|
|
552
|
+
"""
|
|
553
|
+
logger.info(f"📖 Reading resource: {uri}")
|
|
554
|
+
|
|
555
|
+
# Import here to avoid circular dependency
|
|
556
|
+
from .resources import load_resource
|
|
557
|
+
|
|
558
|
+
# Load resource using the existing resource handler (errors handled by decorator)
|
|
559
|
+
result = await load_resource(uri)
|
|
560
|
+
|
|
561
|
+
logger.info(f"✓ Resource loaded successfully: {uri}")
|
|
562
|
+
|
|
563
|
+
# If result is already a dict, return it
|
|
564
|
+
if isinstance(result, dict):
|
|
565
|
+
return {
|
|
566
|
+
"uri": uri,
|
|
567
|
+
"data": result,
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
# If result is a string (JSON), parse it
|
|
571
|
+
import json
|
|
572
|
+
|
|
573
|
+
try:
|
|
574
|
+
data = json.loads(result)
|
|
575
|
+
return {
|
|
576
|
+
"uri": uri,
|
|
577
|
+
"data": data,
|
|
578
|
+
}
|
|
579
|
+
except json.JSONDecodeError:
|
|
580
|
+
# Return as plain text if not JSON
|
|
581
|
+
return {
|
|
582
|
+
"uri": uri,
|
|
583
|
+
"data": {"content": result},
|
|
584
|
+
}
|