remdb 0.3.230__py3-none-any.whl → 0.3.258__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 (40) hide show
  1. rem/agentic/__init__.py +10 -1
  2. rem/agentic/context.py +13 -2
  3. rem/agentic/context_builder.py +45 -34
  4. rem/agentic/providers/pydantic_ai.py +302 -110
  5. rem/api/mcp_router/resources.py +223 -0
  6. rem/api/mcp_router/tools.py +76 -10
  7. rem/api/routers/auth.py +113 -10
  8. rem/api/routers/chat/child_streaming.py +22 -8
  9. rem/api/routers/chat/completions.py +3 -3
  10. rem/api/routers/chat/sse_events.py +3 -3
  11. rem/api/routers/chat/streaming.py +40 -45
  12. rem/api/routers/chat/streaming_utils.py +5 -7
  13. rem/api/routers/feedback.py +2 -2
  14. rem/api/routers/query.py +5 -14
  15. rem/cli/commands/ask.py +144 -33
  16. rem/cli/commands/experiments.py +1 -1
  17. rem/cli/commands/process.py +9 -1
  18. rem/cli/commands/query.py +109 -0
  19. rem/cli/commands/session.py +117 -0
  20. rem/cli/main.py +2 -0
  21. rem/models/core/experiment.py +1 -1
  22. rem/models/entities/session.py +1 -0
  23. rem/schemas/agents/core/agent-builder.yaml +1 -1
  24. rem/schemas/agents/test_orchestrator.yaml +42 -0
  25. rem/schemas/agents/test_structured_output.yaml +52 -0
  26. rem/services/content/providers.py +151 -49
  27. rem/services/postgres/repository.py +1 -0
  28. rem/services/rem/README.md +4 -3
  29. rem/services/rem/parser.py +7 -10
  30. rem/services/rem/service.py +47 -0
  31. rem/services/session/compression.py +7 -3
  32. rem/services/session/pydantic_messages.py +25 -7
  33. rem/services/session/reload.py +2 -1
  34. rem/settings.py +64 -7
  35. rem/sql/migrations/004_cache_system.sql +3 -1
  36. rem/utils/schema_loader.py +135 -103
  37. {remdb-0.3.230.dist-info → remdb-0.3.258.dist-info}/METADATA +6 -5
  38. {remdb-0.3.230.dist-info → remdb-0.3.258.dist-info}/RECORD +40 -37
  39. {remdb-0.3.230.dist-info → remdb-0.3.258.dist-info}/WHEEL +0 -0
  40. {remdb-0.3.230.dist-info → remdb-0.3.258.dist-info}/entry_points.txt +0 -0
rem/agentic/__init__.py CHANGED
@@ -15,7 +15,13 @@ from .schema import (
15
15
  validate_agent_schema,
16
16
  create_agent_schema,
17
17
  )
18
- from .providers.pydantic_ai import create_agent_from_schema_file, create_agent, AgentRuntime
18
+ from .providers.pydantic_ai import (
19
+ create_agent_from_schema_file,
20
+ create_agent,
21
+ AgentRuntime,
22
+ clear_agent_cache,
23
+ get_agent_cache_stats,
24
+ )
19
25
  from .query_helper import ask_rem, REMQueryOutput
20
26
  from .llm_provider_models import (
21
27
  ModelInfo,
@@ -41,6 +47,9 @@ __all__ = [
41
47
  "create_agent_from_schema_file",
42
48
  "create_agent",
43
49
  "AgentRuntime",
50
+ # Agent Cache Management
51
+ "clear_agent_cache",
52
+ "get_agent_cache_stats",
44
53
  # REM Query Helpers
45
54
  "ask_rem",
46
55
  "REMQueryOutput",
rem/agentic/context.py CHANGED
@@ -16,6 +16,7 @@ Headers Mapping:
16
16
  X-Agent-Schema → context.agent_schema_uri (default: "rem")
17
17
  X-Model-Name → context.default_model
18
18
  X-Is-Eval → context.is_eval (marks session as evaluation)
19
+ X-Client-Id → context.client_id (e.g., "web", "mobile", "cli")
19
20
 
20
21
  Key Design Pattern:
21
22
  - AgentContext is passed to agent factory, not stored in agents
@@ -222,6 +223,11 @@ class AgentContext(BaseModel):
222
223
  description="Whether this is an evaluation session (set via X-Is-Eval header)",
223
224
  )
224
225
 
226
+ client_id: str | None = Field(
227
+ default=None,
228
+ description="Client identifier (e.g., 'web', 'mobile', 'cli') set via X-Client-Id header",
229
+ )
230
+
225
231
  model_config = {"populate_by_name": True}
226
232
 
227
233
  def child_context(
@@ -232,7 +238,7 @@ class AgentContext(BaseModel):
232
238
  """
233
239
  Create a child context for nested agent calls.
234
240
 
235
- Inherits user_id, tenant_id, session_id, is_eval from parent.
241
+ Inherits user_id, tenant_id, session_id, is_eval, client_id from parent.
236
242
  Allows overriding agent_schema_uri and default_model for the child.
237
243
 
238
244
  Args:
@@ -256,6 +262,7 @@ class AgentContext(BaseModel):
256
262
  default_model=model_override or self.default_model,
257
263
  agent_schema_uri=agent_schema_uri or self.agent_schema_uri,
258
264
  is_eval=self.is_eval,
265
+ client_id=self.client_id,
259
266
  )
260
267
 
261
268
  @staticmethod
@@ -374,6 +381,7 @@ class AgentContext(BaseModel):
374
381
  default_model=normalized.get("x-model-name") or settings.llm.default_model,
375
382
  agent_schema_uri=normalized.get("x-agent-schema"),
376
383
  is_eval=is_eval,
384
+ client_id=normalized.get("x-client-id"),
377
385
  )
378
386
 
379
387
  @classmethod
@@ -391,6 +399,7 @@ class AgentContext(BaseModel):
391
399
  - X-Model-Name: Model override
392
400
  - X-Agent-Schema: Agent schema URI
393
401
  - X-Is-Eval: Whether this is an evaluation session (true/false)
402
+ - X-Client-Id: Client identifier (e.g., "web", "mobile", "cli")
394
403
 
395
404
  Args:
396
405
  headers: Dictionary of HTTP headers (case-insensitive)
@@ -404,7 +413,8 @@ class AgentContext(BaseModel):
404
413
  "X-Tenant-Id": "acme-corp",
405
414
  "X-Session-Id": "sess-456",
406
415
  "X-Model-Name": "anthropic:claude-opus-4-20250514",
407
- "X-Is-Eval": "true"
416
+ "X-Is-Eval": "true",
417
+ "X-Client-Id": "web"
408
418
  }
409
419
  context = AgentContext.from_headers(headers)
410
420
  """
@@ -422,4 +432,5 @@ class AgentContext(BaseModel):
422
432
  default_model=normalized.get("x-model-name") or settings.llm.default_model,
423
433
  agent_schema_uri=normalized.get("x-agent-schema"),
424
434
  is_eval=is_eval,
435
+ client_id=normalized.get("x-client-id"),
425
436
  )
@@ -4,15 +4,12 @@ Centralized context builder for agent execution.
4
4
  Session History (ALWAYS loaded with compression):
5
5
  - Each chat request is a single message, so session history MUST be recovered
6
6
  - Uses SessionMessageStore with compression to keep context efficient
7
- - Long assistant responses include REM LOOKUP hints: "... [REM LOOKUP session-{id}-msg-{index}] ..."
8
- - Agent can retrieve full content on-demand using REM LOOKUP
9
7
  - Prevents context window bloat while maintaining conversation continuity
10
8
 
11
9
  User Context (on-demand by default):
12
- - System message includes REM LOOKUP hint for user profile
13
- - Agent decides whether to load profile based on query
14
- - More efficient for queries that don't need personalization
15
- - Example: "User: sarah@example.com. To load user profile: Use REM LOOKUP \"sarah@example.com\""
10
+ - System message includes user email for context awareness
11
+ - Fails silently if user not found - agent proceeds without user context
12
+ - Example: "User: sarah@example.com"
16
13
 
17
14
  User Context (auto-inject when enabled):
18
15
  - Set CHAT__AUTO_INJECT_USER_CONTEXT=true
@@ -22,8 +19,8 @@ User Context (auto-inject when enabled):
22
19
  Design Pattern:
23
20
  1. Extract AgentContext from headers (user_id, tenant_id, session_id)
24
21
  2. If auto-inject enabled: Load User/Session from database
25
- 3. If auto-inject disabled: Provide REM LOOKUP hints in system message
26
- 4. Construct system message with date + context (injected or hints)
22
+ 3. If auto-inject disabled: Show user email for context (fail silently if not found)
23
+ 4. Construct system message with date + context
27
24
  5. Return complete context ready for agent execution
28
25
 
29
26
  Integration Points:
@@ -40,11 +37,10 @@ Usage (on-demand, default):
40
37
 
41
38
  # Messages list structure (on-demand):
42
39
  # [
43
- # {"role": "system", "content": "Today's date: 2025-11-22\nUser: sarah@example.com\nTo load user profile: Use REM LOOKUP \"sarah@example.com\"\nSession ID: sess-123\nTo load session history: Use REM LOOKUP messages?session_id=sess-123"},
40
+ # {"role": "system", "content": "Today's date: 2025-11-22\n\nUser: sarah@example.com"},
44
41
  # {"role": "user", "content": "What's next for the API migration?"}
45
42
  # ]
46
43
 
47
- # Agent receives hints and can decide to load context if needed
48
44
  agent = await create_agent(context=context, ...)
49
45
  prompt = "\n".join(msg.content for msg in messages)
50
46
  result = await agent.run(prompt)
@@ -52,7 +48,7 @@ Usage (on-demand, default):
52
48
  Usage (auto-inject, CHAT__AUTO_INJECT_USER_CONTEXT=true):
53
49
  # Messages list structure (auto-inject):
54
50
  # [
55
- # {"role": "system", "content": "Today's date: 2025-11-22\n\nUser Context (auto-injected):\nSummary: ...\nInterests: ...\n\nSession History (auto-injected, 5 messages):"},
51
+ # {"role": "system", "content": "Today's date: 2025-11-22\n\nUser Context (auto-injected):\nSummary: ...\nInterests: ..."},
56
52
  # {"role": "user", "content": "Previous message"},
57
53
  # {"role": "assistant", "content": "Previous response"},
58
54
  # {"role": "user", "content": "What's next for the API migration?"}
@@ -110,13 +106,11 @@ class ContextBuilder:
110
106
 
111
107
  Session History (ALWAYS loaded with compression):
112
108
  - If session_id provided, session history is ALWAYS loaded using SessionMessageStore
113
- - Compression keeps it efficient with REM LOOKUP hints for long messages
114
- - Example: "... [Message truncated - REM LOOKUP session-{id}-msg-{index}] ..."
115
- - Agent can retrieve full content on-demand using REM LOOKUP
109
+ - Compression keeps context efficient
116
110
 
117
111
  User Context (on-demand by default):
118
- - System message includes REM LOOKUP hint: "User: {email}. To load user profile: Use REM LOOKUP \"{email}\""
119
- - Agent decides whether to load profile based on query
112
+ - System message includes user email: "User: {email}"
113
+ - Fails silently if user not found - agent proceeds without user context
120
114
 
121
115
  User Context (auto-inject when enabled):
122
116
  - Set CHAT__AUTO_INJECT_USER_CONTEXT=true
@@ -137,9 +131,9 @@ class ContextBuilder:
137
131
 
138
132
  # messages structure:
139
133
  # [
140
- # {"role": "system", "content": "Today's date: 2025-11-22\nUser: sarah@example.com\nTo load user profile: Use REM LOOKUP \"sarah@example.com\""},
134
+ # {"role": "system", "content": "Today's date: 2025-11-22\n\nUser: sarah@example.com"},
141
135
  # {"role": "user", "content": "Previous message"},
142
- # {"role": "assistant", "content": "Start of long response... [REM LOOKUP session-123-msg-1] ...end"},
136
+ # {"role": "assistant", "content": "Previous response"},
143
137
  # {"role": "user", "content": "New message"}
144
138
  # ]
145
139
  """
@@ -158,6 +152,7 @@ class ContextBuilder:
158
152
  default_model=context.default_model,
159
153
  agent_schema_uri=context.agent_schema_uri,
160
154
  is_eval=context.is_eval,
155
+ client_id=context.client_id,
161
156
  )
162
157
 
163
158
  # Initialize DB if not provided and needed (for user context or session history)
@@ -177,6 +172,10 @@ class ContextBuilder:
177
172
  today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
178
173
  context_hint = f"Today's date: {today}."
179
174
 
175
+ # Add client identifier if present
176
+ if context.client_id:
177
+ context_hint += f"\nClient: {context.client_id}"
178
+
180
179
  # Add user context (auto-inject or on-demand hint)
181
180
  if settings.chat.auto_inject_user_context and context.user_id and db:
182
181
  # Auto-inject: Load and include user profile
@@ -189,18 +188,18 @@ class ContextBuilder:
189
188
  context_hint += f"\n\nUser Context (auto-injected):\n{user_context_content}"
190
189
  else:
191
190
  context_hint += "\n\nNo user context available (anonymous or new user)."
192
- elif context.user_id:
193
- # On-demand: Provide hint to use REM LOOKUP
194
- # user_id is UUID5 hash of email - load user to get email for display and LOOKUP
195
- user_repo = Repository(User, "users", db=db)
196
- user = await user_repo.get_by_id(context.user_id, context.tenant_id)
197
- if user and user.email:
198
- # Show email (more useful than UUID) and LOOKUP hint
199
- context_hint += f"\n\nUser: {user.email}"
200
- context_hint += f"\nTo load user profile: Use REM LOOKUP \"{user.email}\""
201
- else:
202
- context_hint += f"\n\nUser ID: {context.user_id}"
203
- context_hint += "\nUser profile not available."
191
+ elif context.user_id and db:
192
+ # On-demand: Show user email for context (no REM LOOKUP - it requires exact user_id match)
193
+ # Fail silently if user lookup fails - just proceed without user context
194
+ try:
195
+ user_repo = Repository(User, "users", db=db)
196
+ user = await user_repo.get_by_id(context.user_id, context.tenant_id)
197
+ if user and user.email:
198
+ context_hint += f"\n\nUser: {user.email}"
199
+ # If user not found, just proceed without adding user context
200
+ except Exception as e:
201
+ # Fail silently - don't block agent execution if user lookup fails
202
+ logger.debug(f"Could not load user context: {e}")
204
203
 
205
204
  # Add system context hint
206
205
  messages.append(ContextMessage(role="system", content=context_hint))
@@ -221,7 +220,12 @@ class ContextBuilder:
221
220
  # can see previous tool results when the prompt is concatenated
222
221
  for msg_dict in session_history:
223
222
  role = msg_dict["role"]
224
- content = msg_dict["content"]
223
+ content = msg_dict.get("content")
224
+
225
+ # Skip messages with null/empty content (common in tool messages)
226
+ if content is None or content == "":
227
+ logger.debug(f"Skipping {role} message with null/empty content")
228
+ continue
225
229
 
226
230
  if role == "tool":
227
231
  # Wrap tool results with clear markers for visibility
@@ -318,6 +322,7 @@ class ContextBuilder:
318
322
  session_id: str | None = None,
319
323
  message: str = "Hello",
320
324
  model: str | None = None,
325
+ client_id: str | None = None,
321
326
  ) -> tuple[AgentContext, list[ContextMessage]]:
322
327
  """
323
328
  Build context for testing (no database lookup).
@@ -325,7 +330,7 @@ class ContextBuilder:
325
330
  Creates minimal context with:
326
331
  - Test user (test@rem.ai)
327
332
  - Test tenant
328
- - Context hint with date
333
+ - Context hint with date and client
329
334
  - Single user message
330
335
 
331
336
  Args:
@@ -334,6 +339,7 @@ class ContextBuilder:
334
339
  session_id: Optional session ID
335
340
  message: User message content
336
341
  model: Optional model override
342
+ client_id: Optional client identifier (e.g., "cli", "test")
337
343
 
338
344
  Returns:
339
345
  Tuple of (AgentContext, messages list)
@@ -341,7 +347,8 @@ class ContextBuilder:
341
347
  Example:
342
348
  context, messages = await ContextBuilder.build_from_test(
343
349
  user_id="test@rem.ai",
344
- message="What's the weather like?"
350
+ message="What's the weather like?",
351
+ client_id="cli"
345
352
  )
346
353
  """
347
354
  from ..settings import settings
@@ -352,11 +359,15 @@ class ContextBuilder:
352
359
  tenant_id=tenant_id,
353
360
  session_id=session_id,
354
361
  default_model=model or settings.llm.default_model,
362
+ client_id=client_id,
355
363
  )
356
364
 
357
365
  # Build minimal messages
358
366
  today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
359
- context_hint = f"Today's date: {today}.\n\nTest user context: {user_id} (test mode, no profile loaded)."
367
+ context_hint = f"Today's date: {today}."
368
+ if client_id:
369
+ context_hint += f"\nClient: {client_id}"
370
+ context_hint += f"\n\nTest user context: {user_id} (test mode, no profile loaded)."
360
371
 
361
372
  messages = [
362
373
  ContextMessage(role="system", content=context_hint),