remdb 0.2.6__py3-none-any.whl → 0.3.118__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 -2
- rem/agentic/README.md +76 -0
- rem/agentic/__init__.py +15 -0
- rem/agentic/agents/__init__.py +16 -2
- rem/agentic/agents/sse_simulator.py +500 -0
- rem/agentic/context.py +28 -22
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/tool_wrapper.py +29 -3
- rem/agentic/otel/setup.py +92 -4
- rem/agentic/providers/phoenix.py +32 -43
- rem/agentic/providers/pydantic_ai.py +168 -24
- rem/agentic/schema.py +358 -21
- rem/agentic/tools/rem_tools.py +3 -3
- rem/api/README.md +238 -1
- rem/api/deps.py +255 -0
- rem/api/main.py +154 -37
- rem/api/mcp_router/resources.py +1 -1
- rem/api/mcp_router/server.py +26 -5
- rem/api/mcp_router/tools.py +454 -7
- rem/api/middleware/tracking.py +172 -0
- rem/api/routers/admin.py +494 -0
- rem/api/routers/auth.py +124 -0
- rem/api/routers/chat/completions.py +152 -16
- rem/api/routers/chat/models.py +7 -3
- rem/api/routers/chat/sse_events.py +526 -0
- rem/api/routers/chat/streaming.py +608 -45
- rem/api/routers/dev.py +81 -0
- rem/api/routers/feedback.py +148 -0
- rem/api/routers/messages.py +473 -0
- rem/api/routers/models.py +78 -0
- rem/api/routers/query.py +360 -0
- rem/api/routers/shared_sessions.py +406 -0
- rem/auth/middleware.py +126 -27
- rem/cli/commands/README.md +237 -64
- rem/cli/commands/ask.py +15 -11
- rem/cli/commands/cluster.py +1300 -0
- rem/cli/commands/configure.py +170 -97
- rem/cli/commands/db.py +396 -139
- rem/cli/commands/experiments.py +278 -96
- rem/cli/commands/process.py +22 -15
- rem/cli/commands/scaffold.py +47 -0
- rem/cli/commands/schema.py +97 -50
- rem/cli/main.py +37 -6
- rem/config.py +2 -2
- rem/models/core/core_model.py +7 -1
- rem/models/core/rem_query.py +5 -2
- rem/models/entities/__init__.py +21 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/message.py +30 -1
- rem/models/entities/session.py +83 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/user.py +10 -3
- rem/registry.py +373 -0
- rem/schemas/agents/rem.yaml +7 -3
- rem/services/content/providers.py +94 -140
- rem/services/content/service.py +115 -24
- rem/services/dreaming/affinity_service.py +2 -16
- rem/services/dreaming/moment_service.py +2 -15
- rem/services/embeddings/api.py +24 -17
- rem/services/embeddings/worker.py +16 -16
- rem/services/phoenix/EXPERIMENT_DESIGN.md +3 -3
- rem/services/phoenix/client.py +252 -19
- rem/services/postgres/README.md +159 -15
- rem/services/postgres/__init__.py +2 -1
- rem/services/postgres/diff_service.py +531 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +427 -129
- rem/services/postgres/repository.py +132 -0
- rem/services/postgres/schema_generator.py +291 -9
- rem/services/postgres/service.py +6 -6
- rem/services/rate_limit.py +113 -0
- rem/services/rem/README.md +14 -0
- rem/services/rem/parser.py +44 -9
- rem/services/rem/service.py +36 -2
- rem/services/session/compression.py +17 -1
- rem/services/session/reload.py +1 -1
- rem/services/user_service.py +98 -0
- rem/settings.py +169 -22
- rem/sql/background_indexes.sql +21 -16
- rem/sql/migrations/001_install.sql +387 -54
- rem/sql/migrations/002_install_models.sql +2320 -393
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +548 -0
- rem/utils/__init__.py +18 -0
- rem/utils/constants.py +97 -0
- rem/utils/date_utils.py +228 -0
- rem/utils/embeddings.py +17 -4
- rem/utils/files.py +167 -0
- rem/utils/mime_types.py +158 -0
- rem/utils/model_helpers.py +156 -1
- rem/utils/schema_loader.py +284 -21
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +3 -1
- rem/utils/vision.py +9 -14
- rem/workers/README.md +14 -14
- rem/workers/__init__.py +2 -1
- rem/workers/db_maintainer.py +74 -0
- rem/workers/unlogged_maintainer.py +463 -0
- {remdb-0.2.6.dist-info → remdb-0.3.118.dist-info}/METADATA +598 -171
- {remdb-0.2.6.dist-info → remdb-0.3.118.dist-info}/RECORD +102 -73
- {remdb-0.2.6.dist-info → remdb-0.3.118.dist-info}/WHEEL +1 -1
- rem/sql/002_install_models.sql +0 -1068
- rem/sql/install_models.sql +0 -1038
- {remdb-0.2.6.dist-info → remdb-0.3.118.dist-info}/entry_points.txt +0 -0
rem/api/routers/auth.py
CHANGED
|
@@ -49,6 +49,8 @@ from authlib.integrations.starlette_client import OAuth
|
|
|
49
49
|
from loguru import logger
|
|
50
50
|
|
|
51
51
|
from ...settings import settings
|
|
52
|
+
from ...services.postgres.service import PostgresService
|
|
53
|
+
from ...services.user_service import UserService
|
|
52
54
|
|
|
53
55
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
54
56
|
|
|
@@ -168,6 +170,53 @@ async def callback(provider: str, request: Request):
|
|
|
168
170
|
if not user_info:
|
|
169
171
|
# Fetch from userinfo endpoint if not in ID token
|
|
170
172
|
user_info = await client.userinfo(token=token)
|
|
173
|
+
|
|
174
|
+
# --- REM Integration Start ---
|
|
175
|
+
if settings.postgres.enabled:
|
|
176
|
+
# Connect to DB
|
|
177
|
+
db = PostgresService()
|
|
178
|
+
try:
|
|
179
|
+
await db.connect()
|
|
180
|
+
user_service = UserService(db)
|
|
181
|
+
|
|
182
|
+
# Get/Create User
|
|
183
|
+
user_entity = await user_service.get_or_create_user(
|
|
184
|
+
email=user_info.get("email"),
|
|
185
|
+
name=user_info.get("name", "New User"),
|
|
186
|
+
avatar_url=user_info.get("picture"),
|
|
187
|
+
tenant_id="default", # Single tenant for now
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Link Anonymous Session
|
|
191
|
+
# TrackingMiddleware sets request.state.anon_id
|
|
192
|
+
anon_id = getattr(request.state, "anon_id", None)
|
|
193
|
+
# Fallback to cookie if middleware didn't run or state missing
|
|
194
|
+
if not anon_id:
|
|
195
|
+
# Attempt to parse cookie manually if needed, but middleware
|
|
196
|
+
# usually handles the signature logic.
|
|
197
|
+
# Just check raw cookie for simple case (not recommended if signed)
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
if anon_id:
|
|
201
|
+
await user_service.link_anonymous_session(user_entity, anon_id)
|
|
202
|
+
|
|
203
|
+
# Enrich session user with DB info
|
|
204
|
+
db_info = {
|
|
205
|
+
"id": str(user_entity.id),
|
|
206
|
+
"tenant_id": user_entity.tenant_id,
|
|
207
|
+
"tier": user_entity.tier.value if user_entity.tier else "free",
|
|
208
|
+
"roles": [user_entity.role] if user_entity.role else [],
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
except Exception as db_e:
|
|
212
|
+
logger.error(f"Database error during auth callback: {db_e}")
|
|
213
|
+
# Continue login even if DB fails, but warn
|
|
214
|
+
db_info = {"id": "db_error", "tier": "free"}
|
|
215
|
+
finally:
|
|
216
|
+
await db.disconnect()
|
|
217
|
+
else:
|
|
218
|
+
db_info = {"id": "no_db", "tier": "free"}
|
|
219
|
+
# --- REM Integration End ---
|
|
171
220
|
|
|
172
221
|
# Store user info in session
|
|
173
222
|
request.session["user"] = {
|
|
@@ -176,6 +225,11 @@ async def callback(provider: str, request: Request):
|
|
|
176
225
|
"email": user_info.get("email"),
|
|
177
226
|
"name": user_info.get("name"),
|
|
178
227
|
"picture": user_info.get("picture"),
|
|
228
|
+
# Add DB info
|
|
229
|
+
"id": db_info.get("id"),
|
|
230
|
+
"tenant_id": db_info.get("tenant_id", "default"),
|
|
231
|
+
"tier": db_info.get("tier"),
|
|
232
|
+
"roles": db_info.get("roles", []),
|
|
179
233
|
}
|
|
180
234
|
|
|
181
235
|
# Store tokens in session for API access
|
|
@@ -227,3 +281,73 @@ async def me(request: Request):
|
|
|
227
281
|
raise HTTPException(status_code=401, detail="Not authenticated")
|
|
228
282
|
|
|
229
283
|
return user
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# =============================================================================
|
|
287
|
+
# Development Token Endpoints (non-production only)
|
|
288
|
+
# =============================================================================
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def generate_dev_token() -> str:
|
|
292
|
+
"""
|
|
293
|
+
Generate a dev token for testing.
|
|
294
|
+
|
|
295
|
+
Token format: dev_<hmac_signature>
|
|
296
|
+
The signature is based on the session secret to ensure only valid tokens work.
|
|
297
|
+
"""
|
|
298
|
+
import hashlib
|
|
299
|
+
import hmac
|
|
300
|
+
|
|
301
|
+
# Use session secret as key
|
|
302
|
+
secret = settings.auth.session_secret or "dev-secret"
|
|
303
|
+
message = "test-user:dev-token"
|
|
304
|
+
|
|
305
|
+
signature = hmac.new(
|
|
306
|
+
secret.encode(),
|
|
307
|
+
message.encode(),
|
|
308
|
+
hashlib.sha256
|
|
309
|
+
).hexdigest()[:32]
|
|
310
|
+
|
|
311
|
+
return f"dev_{signature}"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def verify_dev_token(token: str) -> bool:
|
|
315
|
+
"""Verify a dev token is valid."""
|
|
316
|
+
expected = generate_dev_token()
|
|
317
|
+
return token == expected
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@router.get("/dev/token")
|
|
321
|
+
async def get_dev_token(request: Request):
|
|
322
|
+
"""
|
|
323
|
+
Get a development token for testing (non-production only).
|
|
324
|
+
|
|
325
|
+
This token can be used as a Bearer token to authenticate as the
|
|
326
|
+
test user (test-user / test@rem.local) without going through OAuth.
|
|
327
|
+
|
|
328
|
+
Usage:
|
|
329
|
+
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/v1/...
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
401 if in production environment
|
|
333
|
+
Token and usage instructions otherwise
|
|
334
|
+
"""
|
|
335
|
+
if settings.environment == "production":
|
|
336
|
+
raise HTTPException(
|
|
337
|
+
status_code=401,
|
|
338
|
+
detail="Dev tokens are not available in production"
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
token = generate_dev_token()
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
"token": token,
|
|
345
|
+
"type": "Bearer",
|
|
346
|
+
"user": {
|
|
347
|
+
"id": "test-user",
|
|
348
|
+
"email": "test@rem.local",
|
|
349
|
+
"name": "Test User",
|
|
350
|
+
},
|
|
351
|
+
"usage": f'curl -H "Authorization: Bearer {token}" http://localhost:8000/api/v1/...',
|
|
352
|
+
"warning": "This token is for development/testing only and will not work in production.",
|
|
353
|
+
}
|
|
@@ -70,7 +70,7 @@ from ....agentic.providers.pydantic_ai import create_agent
|
|
|
70
70
|
from ....services.audio.transcriber import AudioTranscriber
|
|
71
71
|
from ....services.session import SessionMessageStore, reload_session
|
|
72
72
|
from ....settings import settings
|
|
73
|
-
from ....utils.schema_loader import load_agent_schema
|
|
73
|
+
from ....utils.schema_loader import load_agent_schema, load_agent_schema_async
|
|
74
74
|
from .json_utils import extract_json_resilient
|
|
75
75
|
from .models import (
|
|
76
76
|
ChatCompletionChoice,
|
|
@@ -79,9 +79,9 @@ from .models import (
|
|
|
79
79
|
ChatCompletionUsage,
|
|
80
80
|
ChatMessage,
|
|
81
81
|
)
|
|
82
|
-
from .streaming import stream_openai_response
|
|
82
|
+
from .streaming import stream_openai_response, stream_openai_response_with_save, stream_simulator_response
|
|
83
83
|
|
|
84
|
-
router = APIRouter(prefix="/v1", tags=["chat"])
|
|
84
|
+
router = APIRouter(prefix="/api/v1", tags=["chat"])
|
|
85
85
|
|
|
86
86
|
# Default agent schema file
|
|
87
87
|
DEFAULT_AGENT_SCHEMA = "rem"
|
|
@@ -133,9 +133,114 @@ async def chat_completions(body: ChatCompletionRequest, request: Request):
|
|
|
133
133
|
temp_context = AgentContext.from_headers(dict(request.headers))
|
|
134
134
|
schema_name = temp_context.agent_schema_uri or DEFAULT_AGENT_SCHEMA
|
|
135
135
|
|
|
136
|
+
# Resolve model: use body.model if provided, otherwise settings default
|
|
137
|
+
if body.model is None:
|
|
138
|
+
body.model = settings.llm.default_model
|
|
139
|
+
logger.debug(f"No model specified, using default: {body.model}")
|
|
140
|
+
|
|
141
|
+
# Special handling for simulator schema - no LLM, just generates demo SSE events
|
|
142
|
+
# Check BEFORE loading schema since simulator doesn't need a schema file
|
|
143
|
+
# Still builds full context and saves messages like a real agent
|
|
144
|
+
if schema_name == "simulator":
|
|
145
|
+
logger.info("Using SSE simulator (no LLM)")
|
|
146
|
+
|
|
147
|
+
# Build context just like real agents (loads session history, user context)
|
|
148
|
+
new_messages = [msg.model_dump() for msg in body.messages]
|
|
149
|
+
context, messages = await ContextBuilder.build_from_headers(
|
|
150
|
+
headers=dict(request.headers),
|
|
151
|
+
new_messages=new_messages,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Get the last user message as prompt
|
|
155
|
+
prompt = body.messages[-1].content if body.messages else "demo"
|
|
156
|
+
request_id = f"sim-{uuid.uuid4().hex[:24]}"
|
|
157
|
+
|
|
158
|
+
# Generate message IDs upfront for correlation
|
|
159
|
+
user_message_id = str(uuid.uuid4())
|
|
160
|
+
assistant_message_id = str(uuid.uuid4())
|
|
161
|
+
|
|
162
|
+
# Simulated assistant response content (for persistence)
|
|
163
|
+
simulated_content = (
|
|
164
|
+
f"[SSE Simulator Response]\n\n"
|
|
165
|
+
f"This is a simulated response demonstrating all SSE event types:\n"
|
|
166
|
+
f"- reasoning events (model thinking)\n"
|
|
167
|
+
f"- text_delta events (streamed content)\n"
|
|
168
|
+
f"- progress events (multi-step operations)\n"
|
|
169
|
+
f"- tool_call events (function invocations)\n"
|
|
170
|
+
f"- action_request events (UI solicitation)\n"
|
|
171
|
+
f"- metadata events (confidence, sources, message IDs)\n\n"
|
|
172
|
+
f"Original prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Save messages to database (if session_id and postgres enabled)
|
|
176
|
+
if settings.postgres.enabled and context.session_id:
|
|
177
|
+
user_message = {
|
|
178
|
+
"id": user_message_id,
|
|
179
|
+
"role": "user",
|
|
180
|
+
"content": prompt,
|
|
181
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
182
|
+
}
|
|
183
|
+
assistant_message = {
|
|
184
|
+
"id": assistant_message_id,
|
|
185
|
+
"role": "assistant",
|
|
186
|
+
"content": simulated_content,
|
|
187
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
store = SessionMessageStore(user_id=context.user_id or settings.test.effective_user_id)
|
|
192
|
+
await store.store_session_messages(
|
|
193
|
+
session_id=context.session_id,
|
|
194
|
+
messages=[user_message, assistant_message],
|
|
195
|
+
user_id=context.user_id,
|
|
196
|
+
compress=True,
|
|
197
|
+
)
|
|
198
|
+
logger.info(f"Saved simulator conversation to session {context.session_id}")
|
|
199
|
+
except Exception as e:
|
|
200
|
+
# Log error but don't fail the request - session storage is non-critical
|
|
201
|
+
logger.error(f"Failed to save session messages: {e}", exc_info=True)
|
|
202
|
+
|
|
203
|
+
if body.stream:
|
|
204
|
+
return StreamingResponse(
|
|
205
|
+
stream_simulator_response(
|
|
206
|
+
prompt=prompt,
|
|
207
|
+
model="simulator-v1.0.0",
|
|
208
|
+
# Pass message correlation IDs
|
|
209
|
+
message_id=assistant_message_id,
|
|
210
|
+
in_reply_to=user_message_id,
|
|
211
|
+
session_id=context.session_id,
|
|
212
|
+
),
|
|
213
|
+
media_type="text/event-stream",
|
|
214
|
+
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"},
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
# Non-streaming simulator returns simple JSON
|
|
218
|
+
return ChatCompletionResponse(
|
|
219
|
+
id=request_id,
|
|
220
|
+
created=int(time.time()),
|
|
221
|
+
model="simulator-v1.0.0",
|
|
222
|
+
choices=[
|
|
223
|
+
ChatCompletionChoice(
|
|
224
|
+
index=0,
|
|
225
|
+
message=ChatMessage(
|
|
226
|
+
role="assistant",
|
|
227
|
+
content=simulated_content,
|
|
228
|
+
),
|
|
229
|
+
finish_reason="stop",
|
|
230
|
+
)
|
|
231
|
+
],
|
|
232
|
+
usage=ChatCompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0),
|
|
233
|
+
)
|
|
234
|
+
|
|
136
235
|
# Load schema using centralized utility
|
|
236
|
+
# Enable database fallback to load dynamic agents stored in schemas table
|
|
237
|
+
# Use async version since we're in an async context (FastAPI endpoint)
|
|
238
|
+
user_id = temp_context.user_id or settings.test.effective_user_id
|
|
137
239
|
try:
|
|
138
|
-
agent_schema =
|
|
240
|
+
agent_schema = await load_agent_schema_async(
|
|
241
|
+
schema_name,
|
|
242
|
+
user_id=user_id,
|
|
243
|
+
)
|
|
139
244
|
except FileNotFoundError:
|
|
140
245
|
# Fallback to default if specified schema not found
|
|
141
246
|
logger.warning(f"Schema '{schema_name}' not found, falling back to '{DEFAULT_AGENT_SCHEMA}'")
|
|
@@ -151,7 +256,7 @@ async def chat_completions(body: ChatCompletionRequest, request: Request):
|
|
|
151
256
|
detail=f"Agent schema '{schema_name}' not found and default schema unavailable",
|
|
152
257
|
)
|
|
153
258
|
|
|
154
|
-
logger.
|
|
259
|
+
logger.debug(f"Using agent schema: {schema_name}, model: {body.model}")
|
|
155
260
|
|
|
156
261
|
# Check for audio input
|
|
157
262
|
is_audio = request.headers.get("x-chat-is-audio", "").lower() == "true"
|
|
@@ -212,8 +317,35 @@ async def chat_completions(body: ChatCompletionRequest, request: Request):
|
|
|
212
317
|
|
|
213
318
|
# Streaming mode
|
|
214
319
|
if body.stream:
|
|
320
|
+
# Save user message before streaming starts
|
|
321
|
+
if settings.postgres.enabled and context.session_id:
|
|
322
|
+
user_message = {
|
|
323
|
+
"role": "user",
|
|
324
|
+
"content": body.messages[-1].content if body.messages else "",
|
|
325
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
326
|
+
}
|
|
327
|
+
try:
|
|
328
|
+
store = SessionMessageStore(user_id=context.user_id or settings.test.effective_user_id)
|
|
329
|
+
await store.store_session_messages(
|
|
330
|
+
session_id=context.session_id,
|
|
331
|
+
messages=[user_message],
|
|
332
|
+
user_id=context.user_id,
|
|
333
|
+
compress=False, # User messages are typically short
|
|
334
|
+
)
|
|
335
|
+
logger.debug(f"Saved user message to session {context.session_id}")
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f"Failed to save user message: {e}", exc_info=True)
|
|
338
|
+
|
|
215
339
|
return StreamingResponse(
|
|
216
|
-
|
|
340
|
+
stream_openai_response_with_save(
|
|
341
|
+
agent=agent,
|
|
342
|
+
prompt=prompt,
|
|
343
|
+
model=body.model,
|
|
344
|
+
request_id=request_id,
|
|
345
|
+
agent_schema=schema_name,
|
|
346
|
+
session_id=context.session_id,
|
|
347
|
+
user_id=context.user_id,
|
|
348
|
+
),
|
|
217
349
|
media_type="text/event-stream",
|
|
218
350
|
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"},
|
|
219
351
|
)
|
|
@@ -250,17 +382,21 @@ async def chat_completions(body: ChatCompletionRequest, request: Request):
|
|
|
250
382
|
"timestamp": datetime.utcnow().isoformat(),
|
|
251
383
|
}
|
|
252
384
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
385
|
+
try:
|
|
386
|
+
# Store messages with compression
|
|
387
|
+
store = SessionMessageStore(user_id=context.user_id or settings.test.effective_user_id)
|
|
388
|
+
|
|
389
|
+
await store.store_session_messages(
|
|
390
|
+
session_id=context.session_id,
|
|
391
|
+
messages=[user_message, assistant_message],
|
|
392
|
+
user_id=context.user_id,
|
|
393
|
+
compress=True,
|
|
394
|
+
)
|
|
262
395
|
|
|
263
|
-
|
|
396
|
+
logger.info(f"Saved conversation to session {context.session_id}")
|
|
397
|
+
except Exception as e:
|
|
398
|
+
# Log error but don't fail the request - session storage is non-critical
|
|
399
|
+
logger.error(f"Failed to save session messages: {e}", exc_info=True)
|
|
264
400
|
|
|
265
401
|
return ChatCompletionResponse(
|
|
266
402
|
id=request_id,
|
rem/api/routers/chat/models.py
CHANGED
|
@@ -12,6 +12,8 @@ from typing import Literal
|
|
|
12
12
|
|
|
13
13
|
from pydantic import BaseModel, Field
|
|
14
14
|
|
|
15
|
+
from rem.settings import settings
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
# Request models
|
|
17
19
|
class ChatMessage(BaseModel):
|
|
@@ -52,9 +54,11 @@ class ChatCompletionRequest(BaseModel):
|
|
|
52
54
|
Note: Model is specified in body.model (standard OpenAI field), not headers.
|
|
53
55
|
"""
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
# TODO: default should come from settings.llm.default_model at request time
|
|
58
|
+
# Using None and resolving in endpoint to avoid import-time settings evaluation
|
|
59
|
+
model: str | None = Field(
|
|
60
|
+
default=None,
|
|
61
|
+
description="Model to use. Defaults to LLM__DEFAULT_MODEL from settings.",
|
|
58
62
|
)
|
|
59
63
|
messages: list[ChatMessage] = Field(description="Chat conversation history")
|
|
60
64
|
temperature: float | None = Field(default=None, ge=0, le=2)
|