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.

Files changed (235) hide show
  1. rem/__init__.py +129 -0
  2. rem/agentic/README.md +760 -0
  3. rem/agentic/__init__.py +54 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +38 -0
  6. rem/agentic/agents/agent_manager.py +311 -0
  7. rem/agentic/agents/sse_simulator.py +502 -0
  8. rem/agentic/context.py +425 -0
  9. rem/agentic/context_builder.py +360 -0
  10. rem/agentic/llm_provider_models.py +301 -0
  11. rem/agentic/mcp/__init__.py +0 -0
  12. rem/agentic/mcp/tool_wrapper.py +273 -0
  13. rem/agentic/otel/__init__.py +5 -0
  14. rem/agentic/otel/setup.py +240 -0
  15. rem/agentic/providers/phoenix.py +926 -0
  16. rem/agentic/providers/pydantic_ai.py +854 -0
  17. rem/agentic/query.py +117 -0
  18. rem/agentic/query_helper.py +89 -0
  19. rem/agentic/schema.py +737 -0
  20. rem/agentic/serialization.py +245 -0
  21. rem/agentic/tools/__init__.py +5 -0
  22. rem/agentic/tools/rem_tools.py +242 -0
  23. rem/api/README.md +657 -0
  24. rem/api/deps.py +253 -0
  25. rem/api/main.py +460 -0
  26. rem/api/mcp_router/prompts.py +182 -0
  27. rem/api/mcp_router/resources.py +820 -0
  28. rem/api/mcp_router/server.py +243 -0
  29. rem/api/mcp_router/tools.py +1605 -0
  30. rem/api/middleware/tracking.py +172 -0
  31. rem/api/routers/admin.py +520 -0
  32. rem/api/routers/auth.py +898 -0
  33. rem/api/routers/chat/__init__.py +5 -0
  34. rem/api/routers/chat/child_streaming.py +394 -0
  35. rem/api/routers/chat/completions.py +702 -0
  36. rem/api/routers/chat/json_utils.py +76 -0
  37. rem/api/routers/chat/models.py +202 -0
  38. rem/api/routers/chat/otel_utils.py +33 -0
  39. rem/api/routers/chat/sse_events.py +546 -0
  40. rem/api/routers/chat/streaming.py +950 -0
  41. rem/api/routers/chat/streaming_utils.py +327 -0
  42. rem/api/routers/common.py +18 -0
  43. rem/api/routers/dev.py +87 -0
  44. rem/api/routers/feedback.py +276 -0
  45. rem/api/routers/messages.py +620 -0
  46. rem/api/routers/models.py +86 -0
  47. rem/api/routers/query.py +362 -0
  48. rem/api/routers/shared_sessions.py +422 -0
  49. rem/auth/README.md +258 -0
  50. rem/auth/__init__.py +36 -0
  51. rem/auth/jwt.py +367 -0
  52. rem/auth/middleware.py +318 -0
  53. rem/auth/providers/__init__.py +16 -0
  54. rem/auth/providers/base.py +376 -0
  55. rem/auth/providers/email.py +215 -0
  56. rem/auth/providers/google.py +163 -0
  57. rem/auth/providers/microsoft.py +237 -0
  58. rem/cli/README.md +517 -0
  59. rem/cli/__init__.py +8 -0
  60. rem/cli/commands/README.md +299 -0
  61. rem/cli/commands/__init__.py +3 -0
  62. rem/cli/commands/ask.py +549 -0
  63. rem/cli/commands/cluster.py +1808 -0
  64. rem/cli/commands/configure.py +495 -0
  65. rem/cli/commands/db.py +828 -0
  66. rem/cli/commands/dreaming.py +324 -0
  67. rem/cli/commands/experiments.py +1698 -0
  68. rem/cli/commands/mcp.py +66 -0
  69. rem/cli/commands/process.py +388 -0
  70. rem/cli/commands/query.py +109 -0
  71. rem/cli/commands/scaffold.py +47 -0
  72. rem/cli/commands/schema.py +230 -0
  73. rem/cli/commands/serve.py +106 -0
  74. rem/cli/commands/session.py +453 -0
  75. rem/cli/dreaming.py +363 -0
  76. rem/cli/main.py +123 -0
  77. rem/config.py +244 -0
  78. rem/mcp_server.py +41 -0
  79. rem/models/core/__init__.py +49 -0
  80. rem/models/core/core_model.py +70 -0
  81. rem/models/core/engram.py +333 -0
  82. rem/models/core/experiment.py +672 -0
  83. rem/models/core/inline_edge.py +132 -0
  84. rem/models/core/rem_query.py +246 -0
  85. rem/models/entities/__init__.py +68 -0
  86. rem/models/entities/domain_resource.py +38 -0
  87. rem/models/entities/feedback.py +123 -0
  88. rem/models/entities/file.py +57 -0
  89. rem/models/entities/image_resource.py +88 -0
  90. rem/models/entities/message.py +64 -0
  91. rem/models/entities/moment.py +123 -0
  92. rem/models/entities/ontology.py +181 -0
  93. rem/models/entities/ontology_config.py +131 -0
  94. rem/models/entities/resource.py +95 -0
  95. rem/models/entities/schema.py +87 -0
  96. rem/models/entities/session.py +84 -0
  97. rem/models/entities/shared_session.py +180 -0
  98. rem/models/entities/subscriber.py +175 -0
  99. rem/models/entities/user.py +93 -0
  100. rem/py.typed +0 -0
  101. rem/registry.py +373 -0
  102. rem/schemas/README.md +507 -0
  103. rem/schemas/__init__.py +6 -0
  104. rem/schemas/agents/README.md +92 -0
  105. rem/schemas/agents/core/agent-builder.yaml +235 -0
  106. rem/schemas/agents/core/moment-builder.yaml +178 -0
  107. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  108. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  109. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  110. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  111. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  112. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  113. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  114. rem/schemas/agents/examples/hello-world.yaml +37 -0
  115. rem/schemas/agents/examples/query.yaml +54 -0
  116. rem/schemas/agents/examples/simple.yaml +21 -0
  117. rem/schemas/agents/examples/test.yaml +29 -0
  118. rem/schemas/agents/rem.yaml +132 -0
  119. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  120. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  121. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  122. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  123. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  124. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  125. rem/services/__init__.py +18 -0
  126. rem/services/audio/INTEGRATION.md +308 -0
  127. rem/services/audio/README.md +376 -0
  128. rem/services/audio/__init__.py +15 -0
  129. rem/services/audio/chunker.py +354 -0
  130. rem/services/audio/transcriber.py +259 -0
  131. rem/services/content/README.md +1269 -0
  132. rem/services/content/__init__.py +5 -0
  133. rem/services/content/providers.py +760 -0
  134. rem/services/content/service.py +762 -0
  135. rem/services/dreaming/README.md +230 -0
  136. rem/services/dreaming/__init__.py +53 -0
  137. rem/services/dreaming/affinity_service.py +322 -0
  138. rem/services/dreaming/moment_service.py +251 -0
  139. rem/services/dreaming/ontology_service.py +54 -0
  140. rem/services/dreaming/user_model_service.py +297 -0
  141. rem/services/dreaming/utils.py +39 -0
  142. rem/services/email/__init__.py +10 -0
  143. rem/services/email/service.py +522 -0
  144. rem/services/email/templates.py +360 -0
  145. rem/services/embeddings/__init__.py +11 -0
  146. rem/services/embeddings/api.py +127 -0
  147. rem/services/embeddings/worker.py +435 -0
  148. rem/services/fs/README.md +662 -0
  149. rem/services/fs/__init__.py +62 -0
  150. rem/services/fs/examples.py +206 -0
  151. rem/services/fs/examples_paths.py +204 -0
  152. rem/services/fs/git_provider.py +935 -0
  153. rem/services/fs/local_provider.py +760 -0
  154. rem/services/fs/parsing-hooks-examples.md +172 -0
  155. rem/services/fs/paths.py +276 -0
  156. rem/services/fs/provider.py +460 -0
  157. rem/services/fs/s3_provider.py +1042 -0
  158. rem/services/fs/service.py +186 -0
  159. rem/services/git/README.md +1075 -0
  160. rem/services/git/__init__.py +17 -0
  161. rem/services/git/service.py +469 -0
  162. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  163. rem/services/phoenix/README.md +453 -0
  164. rem/services/phoenix/__init__.py +46 -0
  165. rem/services/phoenix/client.py +960 -0
  166. rem/services/phoenix/config.py +88 -0
  167. rem/services/phoenix/prompt_labels.py +477 -0
  168. rem/services/postgres/README.md +757 -0
  169. rem/services/postgres/__init__.py +49 -0
  170. rem/services/postgres/diff_service.py +599 -0
  171. rem/services/postgres/migration_service.py +427 -0
  172. rem/services/postgres/programmable_diff_service.py +635 -0
  173. rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
  174. rem/services/postgres/register_type.py +353 -0
  175. rem/services/postgres/repository.py +481 -0
  176. rem/services/postgres/schema_generator.py +661 -0
  177. rem/services/postgres/service.py +802 -0
  178. rem/services/postgres/sql_builder.py +355 -0
  179. rem/services/rate_limit.py +113 -0
  180. rem/services/rem/README.md +318 -0
  181. rem/services/rem/__init__.py +23 -0
  182. rem/services/rem/exceptions.py +71 -0
  183. rem/services/rem/executor.py +293 -0
  184. rem/services/rem/parser.py +180 -0
  185. rem/services/rem/queries.py +196 -0
  186. rem/services/rem/query.py +371 -0
  187. rem/services/rem/service.py +608 -0
  188. rem/services/session/README.md +374 -0
  189. rem/services/session/__init__.py +13 -0
  190. rem/services/session/compression.py +488 -0
  191. rem/services/session/pydantic_messages.py +310 -0
  192. rem/services/session/reload.py +85 -0
  193. rem/services/user_service.py +130 -0
  194. rem/settings.py +1877 -0
  195. rem/sql/background_indexes.sql +52 -0
  196. rem/sql/migrations/001_install.sql +983 -0
  197. rem/sql/migrations/002_install_models.sql +3157 -0
  198. rem/sql/migrations/003_optional_extensions.sql +326 -0
  199. rem/sql/migrations/004_cache_system.sql +282 -0
  200. rem/sql/migrations/005_schema_update.sql +145 -0
  201. rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
  202. rem/utils/AGENTIC_CHUNKING.md +597 -0
  203. rem/utils/README.md +628 -0
  204. rem/utils/__init__.py +61 -0
  205. rem/utils/agentic_chunking.py +622 -0
  206. rem/utils/batch_ops.py +343 -0
  207. rem/utils/chunking.py +108 -0
  208. rem/utils/clip_embeddings.py +276 -0
  209. rem/utils/constants.py +97 -0
  210. rem/utils/date_utils.py +228 -0
  211. rem/utils/dict_utils.py +98 -0
  212. rem/utils/embeddings.py +436 -0
  213. rem/utils/examples/embeddings_example.py +305 -0
  214. rem/utils/examples/sql_types_example.py +202 -0
  215. rem/utils/files.py +323 -0
  216. rem/utils/markdown.py +16 -0
  217. rem/utils/mime_types.py +158 -0
  218. rem/utils/model_helpers.py +492 -0
  219. rem/utils/schema_loader.py +649 -0
  220. rem/utils/sql_paths.py +146 -0
  221. rem/utils/sql_types.py +350 -0
  222. rem/utils/user_id.py +81 -0
  223. rem/utils/vision.py +325 -0
  224. rem/workers/README.md +506 -0
  225. rem/workers/__init__.py +7 -0
  226. rem/workers/db_listener.py +579 -0
  227. rem/workers/db_maintainer.py +74 -0
  228. rem/workers/dreaming.py +502 -0
  229. rem/workers/engram_processor.py +312 -0
  230. rem/workers/sqs_file_processor.py +193 -0
  231. rem/workers/unlogged_maintainer.py +463 -0
  232. remdb-0.3.242.dist-info/METADATA +1632 -0
  233. remdb-0.3.242.dist-info/RECORD +235 -0
  234. remdb-0.3.242.dist-info/WHEEL +4 -0
  235. remdb-0.3.242.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,245 @@
1
+ """
2
+ Pydantic Serialization Utilities for Agent Results.
3
+
4
+ Critical Pattern:
5
+ When returning Pydantic model instances from agent results (especially in MCP tools,
6
+ API responses, or any serialization context), ALWAYS serialize them explicitly using
7
+ .model_dump() or .model_dump_json() before returning.
8
+
9
+ Why This Matters:
10
+ - FastMCP, FastAPI, and other frameworks may use their own serialization logic
11
+ - Pydantic models returned directly may not include all fields during serialization
12
+ - Newly added fields might be silently dropped if not explicitly serialized
13
+ - result.output or result.data may be a Pydantic model instance, not a dict
14
+
15
+ Common Anti-Patterns to Avoid:
16
+ ```python
17
+ # ❌ BAD: Returns Pydantic model directly
18
+ return {
19
+ "status": "success",
20
+ "response": result.output, # Pydantic model instance!
21
+ }
22
+
23
+ # ✅ GOOD: Explicitly serialize first
24
+ return {
25
+ "status": "success",
26
+ "response": result.output.model_dump(), # Serialized dict
27
+ }
28
+ ```
29
+
30
+ Design Rules:
31
+ 1. Always check if object has .model_dump() or .model_dump_json()
32
+ 2. Use serialize_agent_result() for consistent handling
33
+ 3. In streaming contexts, use .model_dump_json() for SSE
34
+ 4. Document when functions return Pydantic models vs dicts
35
+ """
36
+
37
+ from typing import Any, cast
38
+
39
+ from pydantic import BaseModel
40
+
41
+
42
+ def serialize_agent_result(result: Any) -> dict[str, Any] | str:
43
+ """
44
+ Safely serialize an agent result, handling Pydantic models correctly.
45
+
46
+ This function ensures that Pydantic model instances are properly serialized
47
+ before being returned from API endpoints, MCP tools, or any other context
48
+ where serialization is critical.
49
+
50
+ Args:
51
+ result: Agent result which may be:
52
+ - Pydantic model instance (has .model_dump())
53
+ - Dict (already serialized)
54
+ - Primitive type (str, int, bool, None)
55
+ - List or other collection
56
+
57
+ Returns:
58
+ Serialized result as dict or primitive type
59
+
60
+ Examples:
61
+ >>> # With Pydantic model result
62
+ >>> agent_result = await agent.run(query)
63
+ >>> serialized = serialize_agent_result(agent_result.output)
64
+ >>> return {"response": serialized} # Safe to serialize
65
+
66
+ >>> # With already-serialized result
67
+ >>> data = {"key": "value"}
68
+ >>> serialized = serialize_agent_result(data)
69
+ >>> assert serialized == data # No-op for dicts
70
+
71
+ >>> # With primitive result
72
+ >>> result = "Hello world"
73
+ >>> serialized = serialize_agent_result(result)
74
+ >>> assert serialized == result # No-op for primitives
75
+ """
76
+ # Check if this is a Pydantic model instance
77
+ if isinstance(result, BaseModel):
78
+ return result.model_dump()
79
+
80
+ # Check if this has a model_dump method (duck typing)
81
+ if hasattr(result, "model_dump") and callable(getattr(result, "model_dump")):
82
+ return cast(dict[str, Any] | str, result.model_dump())
83
+
84
+ # Already a dict or primitive - return as-is
85
+ return cast(dict[str, Any] | str, result)
86
+
87
+
88
+ def serialize_agent_result_json(result: Any) -> str:
89
+ """
90
+ Safely serialize an agent result to JSON string, handling Pydantic models correctly.
91
+
92
+ Use this variant when you need a JSON string output (e.g., for SSE streaming,
93
+ JSON responses, or storage).
94
+
95
+ Args:
96
+ result: Agent result which may be:
97
+ - Pydantic model instance (has .model_dump_json())
98
+ - Dict or other JSON-serializable object
99
+ - Primitive type
100
+
101
+ Returns:
102
+ JSON string representation
103
+
104
+ Examples:
105
+ >>> # With Pydantic model result
106
+ >>> agent_result = await agent.run(query)
107
+ >>> json_str = serialize_agent_result_json(agent_result.output)
108
+ >>> return Response(content=json_str, media_type="application/json")
109
+
110
+ >>> # For SSE streaming
111
+ >>> chunk = serialize_agent_result_json(result.output)
112
+ >>> yield f"data: {chunk}\\n\\n"
113
+ """
114
+ import json
115
+
116
+ # Check if this is a Pydantic model instance with model_dump_json
117
+ if isinstance(result, BaseModel):
118
+ return result.model_dump_json()
119
+
120
+ # Check if this has a model_dump_json method (duck typing)
121
+ if hasattr(result, "model_dump_json") and callable(
122
+ getattr(result, "model_dump_json")
123
+ ):
124
+ return cast(str, result.model_dump_json())
125
+
126
+ # Fall back to standard json.dumps
127
+ return json.dumps(result)
128
+
129
+
130
+ def is_pydantic_model(obj: Any) -> bool:
131
+ """
132
+ Check if an object is a Pydantic model instance.
133
+
134
+ Args:
135
+ obj: Object to check
136
+
137
+ Returns:
138
+ True if object is a Pydantic model instance
139
+
140
+ Examples:
141
+ >>> from pydantic import BaseModel
142
+ >>> class MyModel(BaseModel):
143
+ ... value: str
144
+ >>> instance = MyModel(value="test")
145
+ >>> assert is_pydantic_model(instance) == True
146
+ >>> assert is_pydantic_model({"value": "test"}) == False
147
+ """
148
+ return isinstance(obj, BaseModel) or (
149
+ hasattr(obj, "model_dump") and hasattr(obj, "model_fields")
150
+ )
151
+
152
+
153
+ def safe_serialize_dict(data: dict[str, Any]) -> dict[str, Any]:
154
+ """
155
+ Recursively serialize a dict that may contain Pydantic models.
156
+
157
+ Use this when you have a dict that may contain Pydantic model instances
158
+ nested within it (e.g., as values).
159
+
160
+ Args:
161
+ data: Dict that may contain Pydantic models
162
+
163
+ Returns:
164
+ Dict with all Pydantic models serialized to dicts
165
+
166
+ Examples:
167
+ >>> # Dict with nested Pydantic model
168
+ >>> data = {
169
+ ... "status": "success",
170
+ ... "result": some_pydantic_model, # Will be serialized
171
+ ... "metadata": {"count": 5}
172
+ ... }
173
+ >>> serialized = safe_serialize_dict(data)
174
+ >>> # All Pydantic models are now dicts
175
+ """
176
+ result = {}
177
+ for key, value in data.items():
178
+ if is_pydantic_model(value):
179
+ result[key] = serialize_agent_result(value)
180
+ elif isinstance(value, dict):
181
+ result[key] = safe_serialize_dict(value)
182
+ elif isinstance(value, list):
183
+ result[key] = [
184
+ serialize_agent_result(item) if is_pydantic_model(item) else item
185
+ for item in value
186
+ ]
187
+ else:
188
+ result[key] = value
189
+ return result
190
+
191
+
192
+ # Example usage patterns for documentation
193
+ USAGE_EXAMPLES = """
194
+ # Example 1: MCP Tool returning agent result
195
+ async def ask_rem_tool(query: str) -> dict[str, Any]:
196
+ from rem.agentic.serialization import serialize_agent_result
197
+
198
+ agent = await create_agent()
199
+ result = await agent.run(query)
200
+
201
+ # ✅ GOOD: Serialize before returning
202
+ return {
203
+ "status": "success",
204
+ "response": serialize_agent_result(result.output),
205
+ "model": result.model,
206
+ }
207
+
208
+ # Example 2: API endpoint with Pydantic result
209
+ @app.post("/query")
210
+ async def query_endpoint(body: QueryRequest):
211
+ from rem.agentic.serialization import serialize_agent_result
212
+
213
+ agent = await create_agent()
214
+ result = await agent.run(body.query)
215
+
216
+ # ✅ GOOD: Serialize for FastAPI response
217
+ return {
218
+ "data": serialize_agent_result(result.output),
219
+ "usage": result.usage().model_dump() if result.usage() else None,
220
+ }
221
+
222
+ # Example 3: Streaming with SSE
223
+ async def stream_results(agent, query):
224
+ from rem.agentic.serialization import serialize_agent_result_json
225
+
226
+ async with agent.iter(query) as run:
227
+ async for event in run:
228
+ if isinstance(event, SomeEvent):
229
+ # ✅ GOOD: Serialize to JSON string for SSE
230
+ json_str = serialize_agent_result_json(event.data)
231
+ yield f"data: {json_str}\\n\\n"
232
+
233
+ # Example 4: Service layer returning to MCP tool
234
+ async def ask_rem(query: str, tenant_id: str) -> dict[str, Any]:
235
+ from rem.agentic.serialization import serialize_agent_result
236
+
237
+ agent = await create_agent()
238
+ result = await agent.run(query)
239
+
240
+ # ✅ GOOD: Serialize in service layer
241
+ return {
242
+ "query_output": serialize_agent_result(result.data),
243
+ "natural_query": query,
244
+ }
245
+ """
@@ -0,0 +1,5 @@
1
+ """Agent tools for REM operations."""
2
+
3
+ from .rem_tools import search_rem_tool, ingest_file_tool
4
+
5
+ __all__ = ["search_rem_tool", "ingest_file_tool"]
@@ -0,0 +1,242 @@
1
+ """
2
+ REM tools for agent execution (CLI and API compatible).
3
+
4
+ These tools work in both CLI and API contexts by initializing services on-demand.
5
+ They wrap the service layer directly, not MCP tools.
6
+
7
+ Core tables (always available):
8
+ - resources: Documents, content chunks, artifacts
9
+ - moments: Temporal narratives extracted from resources (usually user-specific)
10
+ - ontologies: Domain entities with semantic links for further lookups (like a wiki)
11
+
12
+ Other tables (may vary by deployment):
13
+ - users, sessions, messages, files, schemas, feedbacks
14
+
15
+ Note: Not all tables are populated in all systems. Use FUZZY or SEARCH
16
+ to discover what data exists before assuming specific tables have content.
17
+ """
18
+
19
+ from typing import Any, Literal, cast
20
+
21
+ from loguru import logger
22
+
23
+ from ...models.core import (
24
+ FuzzyParameters,
25
+ LookupParameters,
26
+ QueryType,
27
+ RemQuery,
28
+ SearchParameters,
29
+ SQLParameters,
30
+ TraverseParameters,
31
+ )
32
+ from ...services.content import ContentService
33
+ from ...services.postgres import get_postgres_service
34
+ from ...services.rem import RemService
35
+
36
+
37
+ # Service cache for reuse within agent execution
38
+ _service_cache: dict[str, Any] = {}
39
+
40
+
41
+ async def _get_rem_service() -> RemService:
42
+ """Get or create RemService instance."""
43
+ if "rem_service" not in _service_cache:
44
+ db = get_postgres_service()
45
+ if not db:
46
+ raise RuntimeError("PostgreSQL is disabled. Cannot use REM service.")
47
+
48
+ await db.connect()
49
+ _service_cache["postgres"] = db
50
+ _service_cache["rem_service"] = RemService(postgres_service=db)
51
+ logger.debug("Initialized RemService for agent tools")
52
+ return cast(RemService, _service_cache["rem_service"])
53
+
54
+
55
+ async def search_rem_tool(
56
+ query_type: Literal["lookup", "fuzzy", "search", "sql", "traverse"],
57
+ user_id: str,
58
+ # LOOKUP parameters
59
+ entity_key: str | None = None,
60
+ # FUZZY parameters
61
+ query_text: str | None = None,
62
+ threshold: float = 0.7,
63
+ # SEARCH parameters
64
+ table: str | None = None,
65
+ limit: int = 20,
66
+ # SQL parameters
67
+ sql_query: str | None = None,
68
+ # TRAVERSE parameters
69
+ initial_query: str | None = None,
70
+ edge_types: list[str] | None = None,
71
+ depth: int = 1,
72
+ ) -> dict[str, Any]:
73
+ """
74
+ Execute REM queries for entity lookup, semantic search, and graph traversal.
75
+
76
+ This tool works in both CLI and API contexts by initializing services on-demand.
77
+
78
+ Args:
79
+ query_type: Type of query (lookup, fuzzy, search, sql, traverse)
80
+ user_id: User identifier for data scoping
81
+ entity_key: Entity key for LOOKUP (e.g., "Sarah Chen")
82
+ query_text: Search text for FUZZY or SEARCH
83
+ threshold: Similarity threshold for FUZZY (0.0-1.0)
84
+ table: Target table for SEARCH (resources, moments, users, etc.)
85
+ limit: Max results for SEARCH
86
+ sql_query: SQL query string for SQL type
87
+ initial_query: Starting entity for TRAVERSE
88
+ edge_types: Edge types to follow for TRAVERSE
89
+ depth: Traversal depth for TRAVERSE
90
+
91
+ Returns:
92
+ Dict with query results and metadata
93
+ """
94
+ try:
95
+ rem_service = await _get_rem_service()
96
+
97
+ # Build RemQuery based on query_type
98
+ if query_type == "lookup":
99
+ if not entity_key:
100
+ return {"status": "error", "error": "entity_key required for LOOKUP"}
101
+
102
+ query = RemQuery(
103
+ query_type=QueryType.LOOKUP,
104
+ parameters=LookupParameters(
105
+ key=entity_key,
106
+ user_id=user_id,
107
+ ),
108
+ user_id=user_id,
109
+ )
110
+
111
+ elif query_type == "fuzzy":
112
+ if not query_text:
113
+ return {"status": "error", "error": "query_text required for FUZZY"}
114
+
115
+ query = RemQuery(
116
+ query_type=QueryType.FUZZY,
117
+ parameters=FuzzyParameters(
118
+ query_text=query_text,
119
+ threshold=threshold,
120
+ limit=limit, # Implied parameter
121
+ ),
122
+ user_id=user_id,
123
+ )
124
+
125
+ elif query_type == "search":
126
+ if not query_text:
127
+ return {"status": "error", "error": "query_text required for SEARCH"}
128
+ if not table:
129
+ return {"status": "error", "error": "table required for SEARCH"}
130
+
131
+ query = RemQuery(
132
+ query_type=QueryType.SEARCH,
133
+ parameters=SearchParameters(
134
+ query_text=query_text,
135
+ table_name=table,
136
+ limit=limit,
137
+ ),
138
+ user_id=user_id,
139
+ )
140
+
141
+ elif query_type == "sql":
142
+ if not sql_query:
143
+ return {"status": "error", "error": "sql_query required for SQL"}
144
+
145
+ if not table:
146
+ return {"status": "error", "error": "table required for SQL queries"}
147
+
148
+ query = RemQuery(
149
+ query_type=QueryType.SQL,
150
+ parameters=SQLParameters(
151
+ table_name=table,
152
+ where_clause=sql_query,
153
+ limit=limit, # SQLParams accepts limit
154
+ ),
155
+ user_id=user_id,
156
+ )
157
+
158
+ elif query_type == "traverse":
159
+ if not initial_query:
160
+ return {"status": "error", "error": "initial_query required for TRAVERSE"}
161
+
162
+ query = RemQuery(
163
+ query_type=QueryType.TRAVERSE,
164
+ parameters=TraverseParameters(
165
+ initial_query=initial_query,
166
+ edge_types=edge_types or [],
167
+ max_depth=depth,
168
+ ),
169
+ user_id=user_id,
170
+ )
171
+
172
+ else:
173
+ return {"status": "error", "error": f"Unknown query_type: {query_type}"}
174
+
175
+ # Execute query
176
+ logger.debug(f"Executing REM query: {query_type} for user {user_id}")
177
+ result = await rem_service.execute_query(query)
178
+
179
+ logger.debug(f"Query completed: {query_type}")
180
+ return {
181
+ "status": "success",
182
+ "query_type": query_type,
183
+ "results": result,
184
+ }
185
+
186
+ except Exception as e:
187
+ logger.error(f"search_rem_tool failed: {e}", exc_info=True)
188
+ return {
189
+ "status": "error",
190
+ "error": str(e),
191
+ }
192
+
193
+
194
+ async def ingest_file_tool(
195
+ file_uri: str,
196
+ user_id: str,
197
+ category: str | None = None,
198
+ tags: list[str] | None = None,
199
+ is_local_server: bool = True, # CLI is always local
200
+ ) -> dict[str, Any]:
201
+ """
202
+ Ingest file into REM (read + store + parse + chunk + embed).
203
+
204
+ This tool works in both CLI and API contexts.
205
+
206
+ Args:
207
+ file_uri: File location (local path, s3:// URI, or http(s):// URL)
208
+ user_id: User identifier for data scoping
209
+ category: Optional category (document, code, audio, etc.)
210
+ tags: Optional tags for file
211
+ is_local_server: True if running as local CLI (default)
212
+
213
+ Returns:
214
+ Dict with file_id, processing_status, resources_created, etc.
215
+ """
216
+ try:
217
+ content_service = ContentService()
218
+ result = await content_service.ingest_file(
219
+ file_uri=file_uri,
220
+ user_id=user_id,
221
+ category=category,
222
+ tags=tags,
223
+ is_local_server=is_local_server,
224
+ )
225
+
226
+ logger.debug(
227
+ f"File ingestion complete: {result['file_name']} "
228
+ f"(status: {result['processing_status']}, "
229
+ f"resources: {result['resources_created']})"
230
+ )
231
+
232
+ return {
233
+ "status": "success",
234
+ **result,
235
+ }
236
+
237
+ except Exception as e:
238
+ logger.error(f"ingest_file_tool failed: {e}", exc_info=True)
239
+ return {
240
+ "status": "error",
241
+ "error": str(e),
242
+ }