remdb 0.3.7__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.
Files changed (107) hide show
  1. rem/__init__.py +129 -2
  2. rem/agentic/README.md +76 -0
  3. rem/agentic/__init__.py +15 -0
  4. rem/agentic/agents/__init__.py +16 -2
  5. rem/agentic/agents/sse_simulator.py +502 -0
  6. rem/agentic/context.py +51 -25
  7. rem/agentic/llm_provider_models.py +301 -0
  8. rem/agentic/mcp/tool_wrapper.py +112 -17
  9. rem/agentic/otel/setup.py +93 -4
  10. rem/agentic/providers/phoenix.py +314 -132
  11. rem/agentic/providers/pydantic_ai.py +215 -26
  12. rem/agentic/schema.py +361 -21
  13. rem/agentic/tools/rem_tools.py +3 -3
  14. rem/api/README.md +238 -1
  15. rem/api/deps.py +255 -0
  16. rem/api/main.py +154 -37
  17. rem/api/mcp_router/resources.py +1 -1
  18. rem/api/mcp_router/server.py +26 -5
  19. rem/api/mcp_router/tools.py +465 -7
  20. rem/api/middleware/tracking.py +172 -0
  21. rem/api/routers/admin.py +494 -0
  22. rem/api/routers/auth.py +124 -0
  23. rem/api/routers/chat/completions.py +402 -20
  24. rem/api/routers/chat/models.py +88 -10
  25. rem/api/routers/chat/otel_utils.py +33 -0
  26. rem/api/routers/chat/sse_events.py +542 -0
  27. rem/api/routers/chat/streaming.py +642 -45
  28. rem/api/routers/dev.py +81 -0
  29. rem/api/routers/feedback.py +268 -0
  30. rem/api/routers/messages.py +473 -0
  31. rem/api/routers/models.py +78 -0
  32. rem/api/routers/query.py +360 -0
  33. rem/api/routers/shared_sessions.py +406 -0
  34. rem/auth/middleware.py +126 -27
  35. rem/cli/commands/README.md +237 -64
  36. rem/cli/commands/ask.py +13 -10
  37. rem/cli/commands/cluster.py +1808 -0
  38. rem/cli/commands/configure.py +5 -6
  39. rem/cli/commands/db.py +396 -139
  40. rem/cli/commands/experiments.py +469 -74
  41. rem/cli/commands/process.py +22 -15
  42. rem/cli/commands/scaffold.py +47 -0
  43. rem/cli/commands/schema.py +97 -50
  44. rem/cli/main.py +29 -6
  45. rem/config.py +10 -3
  46. rem/models/core/core_model.py +7 -1
  47. rem/models/core/experiment.py +54 -0
  48. rem/models/core/rem_query.py +5 -2
  49. rem/models/entities/__init__.py +21 -0
  50. rem/models/entities/domain_resource.py +38 -0
  51. rem/models/entities/feedback.py +123 -0
  52. rem/models/entities/message.py +30 -1
  53. rem/models/entities/session.py +83 -0
  54. rem/models/entities/shared_session.py +180 -0
  55. rem/models/entities/user.py +10 -3
  56. rem/registry.py +373 -0
  57. rem/schemas/agents/rem.yaml +7 -3
  58. rem/services/content/providers.py +92 -133
  59. rem/services/content/service.py +92 -20
  60. rem/services/dreaming/affinity_service.py +2 -16
  61. rem/services/dreaming/moment_service.py +2 -15
  62. rem/services/embeddings/api.py +24 -17
  63. rem/services/embeddings/worker.py +16 -16
  64. rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
  65. rem/services/phoenix/client.py +302 -28
  66. rem/services/postgres/README.md +159 -15
  67. rem/services/postgres/__init__.py +2 -1
  68. rem/services/postgres/diff_service.py +531 -0
  69. rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
  70. rem/services/postgres/repository.py +132 -0
  71. rem/services/postgres/schema_generator.py +291 -9
  72. rem/services/postgres/service.py +6 -6
  73. rem/services/rate_limit.py +113 -0
  74. rem/services/rem/README.md +14 -0
  75. rem/services/rem/parser.py +44 -9
  76. rem/services/rem/service.py +36 -2
  77. rem/services/session/compression.py +24 -1
  78. rem/services/session/reload.py +1 -1
  79. rem/services/user_service.py +98 -0
  80. rem/settings.py +399 -29
  81. rem/sql/background_indexes.sql +21 -16
  82. rem/sql/migrations/001_install.sql +387 -54
  83. rem/sql/migrations/002_install_models.sql +2320 -393
  84. rem/sql/migrations/003_optional_extensions.sql +326 -0
  85. rem/sql/migrations/004_cache_system.sql +548 -0
  86. rem/utils/__init__.py +18 -0
  87. rem/utils/constants.py +97 -0
  88. rem/utils/date_utils.py +228 -0
  89. rem/utils/embeddings.py +17 -4
  90. rem/utils/files.py +167 -0
  91. rem/utils/mime_types.py +158 -0
  92. rem/utils/model_helpers.py +156 -1
  93. rem/utils/schema_loader.py +282 -35
  94. rem/utils/sql_paths.py +146 -0
  95. rem/utils/sql_types.py +3 -1
  96. rem/utils/vision.py +9 -14
  97. rem/workers/README.md +14 -14
  98. rem/workers/__init__.py +3 -1
  99. rem/workers/db_listener.py +579 -0
  100. rem/workers/db_maintainer.py +74 -0
  101. rem/workers/unlogged_maintainer.py +463 -0
  102. {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/METADATA +460 -303
  103. {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/RECORD +105 -74
  104. {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/WHEEL +1 -1
  105. rem/sql/002_install_models.sql +0 -1068
  106. rem/sql/install_models.sql +0 -1038
  107. {remdb-0.3.7.dist-info → remdb-0.3.133.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,360 @@
1
+ """
2
+ REM Query API - Execute REM dialect or natural language queries.
3
+
4
+ Endpoints:
5
+ POST /api/v1/query - Execute a REM query
6
+
7
+ Modes:
8
+ - rem-dialect (default): Execute REM query syntax directly
9
+ Example: "LOOKUP sarah-chen", "SEARCH resources 'API design'", "TRAVERSE FROM doc-123 DEPTH 2"
10
+
11
+ - natural-language: Convert natural language to REM query via LLM agent
12
+ Example: "Find all documents by Sarah", "What meetings happened last week?"
13
+
14
+ - staged-plan: Execute a multi-stage query plan (query field is ignored)
15
+ Example: Execute a sequence of queries with context passing between stages
16
+ Status: TODO - signature only, implementation pending in RemService
17
+
18
+ Model Selection:
19
+ Default model: openai:gpt-4.1 (widely available, good balance of speed/quality)
20
+
21
+ Recommended for speed: cerebras:qwen-3-32b
22
+ - Cerebras provides extremely fast inference (~1000 tokens/sec)
23
+ - Set CEREBRAS_API_KEY environment variable
24
+ - Pass model="cerebras:qwen-3-32b" in request
25
+
26
+ Example:
27
+ # REM dialect (default)
28
+ curl -X POST http://localhost:8000/api/v1/query \\
29
+ -H "Content-Type: application/json" \\
30
+ -H "X-User-Id: user123" \\
31
+ -d '{"query": "LOOKUP sarah-chen"}'
32
+
33
+ # Natural language
34
+ curl -X POST http://localhost:8000/api/v1/query \\
35
+ -H "Content-Type: application/json" \\
36
+ -H "X-User-Id: user123" \\
37
+ -d '{"query": "Find all documents about API design", "mode": "natural-language"}'
38
+
39
+ # With Cerebras for speed
40
+ curl -X POST http://localhost:8000/api/v1/query \\
41
+ -H "Content-Type: application/json" \\
42
+ -H "X-User-Id: user123" \\
43
+ -d '{"query": "Who is Sarah?", "mode": "natural-language", "model": "cerebras:qwen-3-32b"}'
44
+
45
+ # Staged plan (TODO) - static query stages
46
+ curl -X POST http://localhost:8000/api/v1/query \\
47
+ -H "Content-Type: application/json" \\
48
+ -H "X-User-Id: user123" \\
49
+ -d '{"mode": "staged-plan", "plan": [
50
+ {"stage": 1, "query": "LOOKUP Sarah Chen", "name": "user"},
51
+ {"stage": 2, "query": "TRAVERSE FROM \"Sarah Chen\" DEPTH 2"}
52
+ ]}'
53
+
54
+ # Staged plan with LLM-driven dynamic stages
55
+ curl -X POST http://localhost:8000/api/v1/query \\
56
+ -H "Content-Type: application/json" \\
57
+ -H "X-User-Id: user123" \\
58
+ -d '{"mode": "staged-plan", "plan": [
59
+ {"stage": 1, "query": "LOOKUP Sarah Chen", "name": "user"},
60
+ {"stage": 2, "intent": "find her team members", "depends_on": ["user"]}
61
+ ]}'
62
+
63
+ # Plan continuation - pass previous_results to resume a multi-turn plan
64
+ # Turn 1: Execute stage 1, get back stage_results
65
+ # Turn 2: Continue with stage 2, passing previous results
66
+ curl -X POST http://localhost:8000/api/v1/query \\
67
+ -H "Content-Type: application/json" \\
68
+ -H "X-User-Id: user123" \\
69
+ -d '{
70
+ "mode": "staged-plan",
71
+ "plan": [
72
+ {"stage": 1, "query": "LOOKUP Sarah Chen", "name": "user"},
73
+ {"stage": 2, "intent": "find her team members", "depends_on": ["user"]}
74
+ ],
75
+ "previous_results": [
76
+ {"stage": 1, "name": "user", "query_executed": "LOOKUP Sarah Chen", "results": [...], "count": 1}
77
+ ],
78
+ "resume_from_stage": 2
79
+ }'
80
+ """
81
+
82
+ from enum import Enum
83
+ from typing import Any
84
+
85
+ from fastapi import APIRouter, Header, HTTPException
86
+ from loguru import logger
87
+ from pydantic import BaseModel, Field
88
+
89
+ from ...services.postgres import get_postgres_service
90
+ from ...services.rem.service import RemService
91
+ from ...services.rem.parser import RemQueryParser
92
+ from ...models.core import RemQuery
93
+ from ...settings import settings
94
+
95
+ router = APIRouter(prefix="/api/v1", tags=["query"])
96
+
97
+
98
+ class QueryMode(str, Enum):
99
+ """Query execution mode."""
100
+ REM_DIALECT = "rem-dialect"
101
+ NATURAL_LANGUAGE = "natural-language"
102
+ STAGED_PLAN = "staged-plan"
103
+
104
+
105
+ class StagedPlanResult(BaseModel):
106
+ """Result from a completed stage - used for plan continuation."""
107
+
108
+ stage: int = Field(..., description="Stage number that produced this result")
109
+ name: str | None = Field(default=None, description="Stage name for referencing")
110
+ query_executed: str = Field(..., description="The REM query that was executed")
111
+ results: list[dict[str, Any]] = Field(default_factory=list, description="Query results")
112
+ count: int = Field(default=0, description="Number of results")
113
+
114
+
115
+ class QueryPlanStage(BaseModel):
116
+ """A single stage in a multi-stage query plan.
117
+
118
+ Each stage can be either:
119
+ 1. A static REM dialect query (query field set)
120
+ 2. A dynamic query built by LLM from intent + previous results (intent field set)
121
+
122
+ The LLM interprets the intent along with previous stage results to construct
123
+ the appropriate REM query at runtime.
124
+ """
125
+
126
+ stage: int = Field(..., description="Stage number (1-indexed, executed in order)")
127
+ query: str | None = Field(
128
+ default=None,
129
+ description="Static REM dialect query (mutually exclusive with intent)",
130
+ )
131
+ intent: str | None = Field(
132
+ default=None,
133
+ description="Natural language intent - LLM builds query from this + previous results",
134
+ )
135
+ name: str | None = Field(default=None, description="Optional name for referencing results")
136
+ depends_on: list[str] | None = Field(
137
+ default=None,
138
+ description="Names of previous stages whose results are passed as context to LLM",
139
+ )
140
+
141
+
142
+ class QueryRequest(BaseModel):
143
+ """Request body for REM query execution."""
144
+
145
+ query: str | None = Field(
146
+ default=None,
147
+ description="Query string - either REM dialect syntax or natural language. Required for rem-dialect and natural-language modes.",
148
+ examples=[
149
+ "LOOKUP sarah-chen",
150
+ "SEARCH resources 'API design' LIMIT 10",
151
+ "Find all documents by Sarah",
152
+ ],
153
+ )
154
+
155
+ mode: QueryMode = Field(
156
+ default=QueryMode.REM_DIALECT,
157
+ description="Query mode: 'rem-dialect' (default), 'natural-language', or 'staged-plan'",
158
+ )
159
+
160
+ model: str = Field(
161
+ default="openai:gpt-4.1",
162
+ description=(
163
+ "LLM model for natural-language mode. "
164
+ "Default: openai:gpt-4.1. "
165
+ "Recommended for speed: cerebras:qwen-3-32b (requires CEREBRAS_API_KEY)"
166
+ ),
167
+ )
168
+
169
+ plan_only: bool = Field(
170
+ default=False,
171
+ description="If true with natural-language mode, return generated query without executing",
172
+ )
173
+
174
+ plan: list[QueryPlanStage] | None = Field(
175
+ default=None,
176
+ description="Multi-stage query plan for staged-plan mode. Each stage executes in order.",
177
+ )
178
+
179
+ previous_results: list[StagedPlanResult] | None = Field(
180
+ default=None,
181
+ description=(
182
+ "Results from previous turns for plan continuation. "
183
+ "Pass this back from the response's stage_results to continue a multi-turn plan."
184
+ ),
185
+ )
186
+
187
+ resume_from_stage: int | None = Field(
188
+ default=None,
189
+ description="Stage number to resume from (1-indexed). Stages before this are skipped.",
190
+ )
191
+
192
+
193
+ class QueryResponse(BaseModel):
194
+ """Response from REM query execution."""
195
+
196
+ query_type: str = Field(..., description="Type of query executed (LOOKUP, SEARCH, FUZZY, SQL, TRAVERSE)")
197
+ query: str = Field(..., description="The query that was executed (original or generated)")
198
+ results: list[dict[str, Any]] = Field(default_factory=list, description="Query results")
199
+ count: int = Field(..., description="Number of results")
200
+
201
+ # Natural language mode fields
202
+ mode: QueryMode = Field(..., description="Query mode used")
203
+ generated_query: str | None = Field(default=None, description="Generated REM query (natural-language mode only)")
204
+ confidence: float | None = Field(default=None, description="Confidence score (natural-language mode only)")
205
+ reasoning: str | None = Field(default=None, description="Query reasoning (natural-language mode only)")
206
+ warning: str | None = Field(default=None, description="Warning message if any")
207
+ plan_only: bool = Field(default=False, description="If true, query was not executed (plan mode)")
208
+
209
+ # Staged plan mode fields
210
+ stage_results: list[dict[str, Any]] | None = Field(
211
+ default=None,
212
+ description="Results from each stage (staged-plan mode only)",
213
+ )
214
+
215
+
216
+ @router.post("/query", response_model=QueryResponse)
217
+ async def execute_query(
218
+ request: QueryRequest,
219
+ x_user_id: str | None = Header(default=None, description="User ID for query isolation (optional, uses default if not provided)"),
220
+ ) -> QueryResponse:
221
+ """
222
+ Execute a REM query.
223
+
224
+ Supports three modes:
225
+
226
+ **rem-dialect** (default): Execute REM query syntax directly.
227
+ - LOOKUP "entity-key" - O(1) key-value lookup
228
+ - FUZZY "text" THRESHOLD 0.3 - Fuzzy text matching
229
+ - SEARCH table "semantic query" LIMIT 10 - Vector similarity search
230
+ - TRAVERSE FROM "entity" TYPE "rel" DEPTH 2 - Graph traversal
231
+ - SQL SELECT * FROM table WHERE ... - Direct SQL (SELECT only)
232
+
233
+ **natural-language**: Convert question to REM query via LLM.
234
+ - Uses REM Query Agent to parse intent
235
+ - Auto-executes if confidence >= 0.7
236
+ - Returns warning for low-confidence queries
237
+
238
+ **staged-plan**: Execute a multi-stage query plan.
239
+ - Pass plan=[{stage: 1, query: "...", name: "..."}, ...] instead of query
240
+ - Stages execute in order with context passing between them
241
+ - TODO: Implementation pending in RemService
242
+
243
+ **Model Selection**:
244
+ - Default: openai:gpt-4.1 (reliable, widely available)
245
+ - Speed: cerebras:qwen-3-32b (requires CEREBRAS_API_KEY)
246
+
247
+ Returns:
248
+ QueryResponse with results and metadata
249
+ """
250
+ if not settings.postgres.enabled:
251
+ raise HTTPException(
252
+ status_code=503,
253
+ detail="Database not configured. Set POSTGRES__ENABLED=true",
254
+ )
255
+
256
+ try:
257
+ # Get database service and ensure connected
258
+ db = get_postgres_service()
259
+ if db is None:
260
+ raise HTTPException(status_code=503, detail="Database service unavailable")
261
+
262
+ # Connect if not already connected
263
+ if db.pool is None:
264
+ await db.connect()
265
+
266
+ rem_service = RemService(db)
267
+
268
+ # Use effective_user_id from settings if not provided
269
+ effective_user_id = x_user_id or settings.test.effective_user_id
270
+
271
+ if request.mode == QueryMode.STAGED_PLAN:
272
+ # Staged plan mode - execute multi-stage query plan
273
+ # TODO: Implementation pending in RemService.execute_staged_plan()
274
+ if not request.plan:
275
+ raise HTTPException(
276
+ status_code=400,
277
+ detail="staged-plan mode requires 'plan' field with list of QueryPlanStage",
278
+ )
279
+
280
+ logger.info(f"Staged plan query: {len(request.plan)} stages")
281
+
282
+ # TODO: Call rem_service.execute_staged_plan(request.plan, x_user_id)
283
+ # For now, return a 501 Not Implemented
284
+ raise HTTPException(
285
+ status_code=501,
286
+ detail="staged-plan mode not yet implemented. See RemService TODO.",
287
+ )
288
+
289
+ elif request.mode == QueryMode.NATURAL_LANGUAGE:
290
+ # Natural language mode - use agent to convert
291
+ if not request.query:
292
+ raise HTTPException(
293
+ status_code=400,
294
+ detail="natural-language mode requires 'query' field",
295
+ )
296
+
297
+ logger.info(f"Natural language query: {request.query[:100]}... (model={request.model})")
298
+
299
+ result = await rem_service.ask_rem(
300
+ natural_query=request.query,
301
+ tenant_id=effective_user_id,
302
+ llm_model=request.model,
303
+ plan_mode=request.plan_only,
304
+ )
305
+
306
+ # Build response
307
+ response = QueryResponse(
308
+ query_type=result.get("results", {}).get("query_type", "UNKNOWN"),
309
+ query=request.query,
310
+ results=result.get("results", {}).get("results", []),
311
+ count=result.get("results", {}).get("count", 0),
312
+ mode=QueryMode.NATURAL_LANGUAGE,
313
+ generated_query=result.get("query"),
314
+ confidence=result.get("confidence"),
315
+ reasoning=result.get("reasoning"),
316
+ warning=result.get("warning"),
317
+ plan_only=result.get("plan_mode", False),
318
+ )
319
+
320
+ return response
321
+
322
+ else:
323
+ # REM dialect mode - parse and execute directly
324
+ if not request.query:
325
+ raise HTTPException(
326
+ status_code=400,
327
+ detail="rem-dialect mode requires 'query' field",
328
+ )
329
+
330
+ logger.info(f"REM dialect query: {request.query[:100]}...")
331
+
332
+ parser = RemQueryParser()
333
+ query_type, parameters = parser.parse(request.query)
334
+
335
+ # Create and execute RemQuery
336
+ rem_query = RemQuery.model_validate({
337
+ "query_type": query_type,
338
+ "parameters": parameters,
339
+ "user_id": effective_user_id,
340
+ })
341
+
342
+ result = await rem_service.execute_query(rem_query)
343
+
344
+ return QueryResponse(
345
+ query_type=result["query_type"],
346
+ query=request.query,
347
+ results=result.get("results", []),
348
+ count=result.get("count", 0),
349
+ mode=QueryMode.REM_DIALECT,
350
+ )
351
+
352
+ except HTTPException:
353
+ # Re-raise HTTPExceptions (400, 501, etc.) without wrapping
354
+ raise
355
+ except ValueError as e:
356
+ # Parse errors
357
+ raise HTTPException(status_code=400, detail=str(e))
358
+ except Exception as e:
359
+ logger.exception(f"Query execution failed: {e}")
360
+ raise HTTPException(status_code=500, detail=f"Query execution failed: {str(e)}")