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/api/routers/query.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
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 .common import ErrorResponse
|
|
90
|
+
|
|
91
|
+
from ...services.postgres import get_postgres_service
|
|
92
|
+
from ...services.rem.service import RemService
|
|
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(
|
|
217
|
+
"/query",
|
|
218
|
+
response_model=QueryResponse,
|
|
219
|
+
responses={
|
|
220
|
+
400: {"model": ErrorResponse, "description": "Invalid query or missing required fields"},
|
|
221
|
+
500: {"model": ErrorResponse, "description": "Query execution failed"},
|
|
222
|
+
501: {"model": ErrorResponse, "description": "Feature not yet implemented"},
|
|
223
|
+
503: {"model": ErrorResponse, "description": "Database not configured or unavailable"},
|
|
224
|
+
},
|
|
225
|
+
)
|
|
226
|
+
async def execute_query(
|
|
227
|
+
request: QueryRequest,
|
|
228
|
+
x_user_id: str | None = Header(default=None, description="User ID for query isolation (optional, uses default if not provided)"),
|
|
229
|
+
) -> QueryResponse:
|
|
230
|
+
"""
|
|
231
|
+
Execute a REM query.
|
|
232
|
+
|
|
233
|
+
Supports three modes:
|
|
234
|
+
|
|
235
|
+
**rem-dialect** (default): Execute REM query syntax directly.
|
|
236
|
+
- LOOKUP "entity-key" - O(1) key-value lookup
|
|
237
|
+
- FUZZY "text" THRESHOLD 0.3 - Fuzzy text matching
|
|
238
|
+
- SEARCH table "semantic query" LIMIT 10 - Vector similarity search
|
|
239
|
+
- TRAVERSE FROM "entity" TYPE "rel" DEPTH 2 - Graph traversal
|
|
240
|
+
- SQL SELECT * FROM table WHERE ... - Direct SQL (SELECT only)
|
|
241
|
+
|
|
242
|
+
**natural-language**: Convert question to REM query via LLM.
|
|
243
|
+
- Uses REM Query Agent to parse intent
|
|
244
|
+
- Auto-executes if confidence >= 0.7
|
|
245
|
+
- Returns warning for low-confidence queries
|
|
246
|
+
|
|
247
|
+
**staged-plan**: Execute a multi-stage query plan.
|
|
248
|
+
- Pass plan=[{stage: 1, query: "...", name: "..."}, ...] instead of query
|
|
249
|
+
- Stages execute in order with context passing between them
|
|
250
|
+
- TODO: Implementation pending in RemService
|
|
251
|
+
|
|
252
|
+
**Model Selection**:
|
|
253
|
+
- Default: openai:gpt-4.1 (reliable, widely available)
|
|
254
|
+
- Speed: cerebras:qwen-3-32b (requires CEREBRAS_API_KEY)
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
QueryResponse with results and metadata
|
|
258
|
+
"""
|
|
259
|
+
if not settings.postgres.enabled:
|
|
260
|
+
raise HTTPException(
|
|
261
|
+
status_code=503,
|
|
262
|
+
detail="Database not configured. Set POSTGRES__ENABLED=true",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
# Get database service and ensure connected
|
|
267
|
+
db = get_postgres_service()
|
|
268
|
+
if db is None:
|
|
269
|
+
raise HTTPException(status_code=503, detail="Database service unavailable")
|
|
270
|
+
|
|
271
|
+
# Connect if not already connected
|
|
272
|
+
if db.pool is None:
|
|
273
|
+
await db.connect()
|
|
274
|
+
|
|
275
|
+
rem_service = RemService(db)
|
|
276
|
+
|
|
277
|
+
# Use effective_user_id from settings if not provided
|
|
278
|
+
effective_user_id = x_user_id or settings.test.effective_user_id
|
|
279
|
+
|
|
280
|
+
if request.mode == QueryMode.STAGED_PLAN:
|
|
281
|
+
# Staged plan mode - execute multi-stage query plan
|
|
282
|
+
# TODO: Implementation pending in RemService.execute_staged_plan()
|
|
283
|
+
if not request.plan:
|
|
284
|
+
raise HTTPException(
|
|
285
|
+
status_code=400,
|
|
286
|
+
detail="staged-plan mode requires 'plan' field with list of QueryPlanStage",
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
logger.info(f"Staged plan query: {len(request.plan)} stages")
|
|
290
|
+
|
|
291
|
+
# TODO: Call rem_service.execute_staged_plan(request.plan, x_user_id)
|
|
292
|
+
# For now, return a 501 Not Implemented
|
|
293
|
+
raise HTTPException(
|
|
294
|
+
status_code=501,
|
|
295
|
+
detail="staged-plan mode not yet implemented. See RemService TODO.",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
elif request.mode == QueryMode.NATURAL_LANGUAGE:
|
|
299
|
+
# Natural language mode - use agent to convert
|
|
300
|
+
if not request.query:
|
|
301
|
+
raise HTTPException(
|
|
302
|
+
status_code=400,
|
|
303
|
+
detail="natural-language mode requires 'query' field",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
logger.info(f"Natural language query: {request.query[:100]}... (model={request.model})")
|
|
307
|
+
|
|
308
|
+
result = await rem_service.ask_rem(
|
|
309
|
+
natural_query=request.query,
|
|
310
|
+
tenant_id=effective_user_id,
|
|
311
|
+
llm_model=request.model,
|
|
312
|
+
plan_mode=request.plan_only,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Build response
|
|
316
|
+
response = QueryResponse(
|
|
317
|
+
query_type=result.get("results", {}).get("query_type", "UNKNOWN"),
|
|
318
|
+
query=request.query,
|
|
319
|
+
results=result.get("results", {}).get("results", []),
|
|
320
|
+
count=result.get("results", {}).get("count", 0),
|
|
321
|
+
mode=QueryMode.NATURAL_LANGUAGE,
|
|
322
|
+
generated_query=result.get("query"),
|
|
323
|
+
confidence=result.get("confidence"),
|
|
324
|
+
reasoning=result.get("reasoning"),
|
|
325
|
+
warning=result.get("warning"),
|
|
326
|
+
plan_only=result.get("plan_mode", False),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return response
|
|
330
|
+
|
|
331
|
+
else:
|
|
332
|
+
# REM dialect mode - use unified execute_query_string
|
|
333
|
+
if not request.query:
|
|
334
|
+
raise HTTPException(
|
|
335
|
+
status_code=400,
|
|
336
|
+
detail="rem-dialect mode requires 'query' field",
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
logger.info(f"REM dialect query: {request.query[:100]}...")
|
|
340
|
+
|
|
341
|
+
# Use the unified execute_query_string method
|
|
342
|
+
result = await rem_service.execute_query_string(
|
|
343
|
+
request.query, user_id=effective_user_id
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return QueryResponse(
|
|
347
|
+
query_type=result["query_type"],
|
|
348
|
+
query=request.query,
|
|
349
|
+
results=result.get("results", []),
|
|
350
|
+
count=result.get("count", 0),
|
|
351
|
+
mode=QueryMode.REM_DIALECT,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
except HTTPException:
|
|
355
|
+
# Re-raise HTTPExceptions (400, 501, etc.) without wrapping
|
|
356
|
+
raise
|
|
357
|
+
except ValueError as e:
|
|
358
|
+
# Parse errors
|
|
359
|
+
raise HTTPException(status_code=400, detail=str(e))
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.exception(f"Query execution failed: {e}")
|
|
362
|
+
raise HTTPException(status_code=500, detail=f"Query execution failed: {str(e)}")
|