remdb 0.3.180__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.
- rem/agentic/README.md +36 -2
- rem/agentic/__init__.py +10 -1
- rem/agentic/context.py +185 -1
- rem/agentic/context_builder.py +56 -35
- rem/agentic/mcp/tool_wrapper.py +2 -2
- rem/agentic/providers/pydantic_ai.py +303 -111
- rem/agentic/schema.py +2 -2
- rem/api/main.py +1 -1
- rem/api/mcp_router/resources.py +223 -0
- rem/api/mcp_router/server.py +4 -0
- rem/api/mcp_router/tools.py +608 -166
- rem/api/routers/admin.py +30 -4
- rem/api/routers/auth.py +219 -20
- rem/api/routers/chat/child_streaming.py +393 -0
- rem/api/routers/chat/completions.py +77 -40
- rem/api/routers/chat/sse_events.py +7 -3
- rem/api/routers/chat/streaming.py +381 -291
- rem/api/routers/chat/streaming_utils.py +325 -0
- rem/api/routers/common.py +18 -0
- rem/api/routers/dev.py +7 -1
- rem/api/routers/feedback.py +11 -3
- rem/api/routers/messages.py +176 -38
- rem/api/routers/models.py +9 -1
- rem/api/routers/query.py +17 -15
- rem/api/routers/shared_sessions.py +16 -0
- rem/auth/jwt.py +19 -4
- rem/auth/middleware.py +42 -28
- rem/cli/README.md +62 -0
- rem/cli/commands/ask.py +205 -114
- rem/cli/commands/db.py +55 -31
- rem/cli/commands/experiments.py +1 -1
- rem/cli/commands/process.py +179 -43
- rem/cli/commands/query.py +109 -0
- rem/cli/commands/session.py +117 -0
- rem/cli/main.py +2 -0
- rem/models/core/experiment.py +1 -1
- rem/models/entities/ontology.py +18 -20
- rem/models/entities/session.py +1 -0
- rem/schemas/agents/core/agent-builder.yaml +1 -1
- rem/schemas/agents/rem.yaml +1 -1
- rem/schemas/agents/test_orchestrator.yaml +42 -0
- rem/schemas/agents/test_structured_output.yaml +52 -0
- rem/services/content/providers.py +151 -49
- rem/services/content/service.py +18 -5
- rem/services/embeddings/worker.py +26 -12
- rem/services/postgres/__init__.py +28 -3
- rem/services/postgres/diff_service.py +57 -5
- rem/services/postgres/programmable_diff_service.py +635 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +2 -2
- rem/services/postgres/register_type.py +11 -10
- rem/services/postgres/repository.py +39 -28
- rem/services/postgres/schema_generator.py +5 -5
- rem/services/postgres/sql_builder.py +6 -5
- rem/services/rem/README.md +4 -3
- rem/services/rem/parser.py +7 -10
- rem/services/rem/service.py +47 -0
- rem/services/session/__init__.py +8 -1
- rem/services/session/compression.py +47 -5
- rem/services/session/pydantic_messages.py +310 -0
- rem/services/session/reload.py +2 -1
- rem/settings.py +92 -7
- rem/sql/migrations/001_install.sql +125 -7
- rem/sql/migrations/002_install_models.sql +159 -149
- rem/sql/migrations/004_cache_system.sql +10 -276
- rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
- rem/utils/schema_loader.py +180 -120
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/METADATA +7 -6
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/RECORD +70 -61
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/WHEEL +0 -0
- {remdb-0.3.180.dist-info → remdb-0.3.258.dist-info}/entry_points.txt +0 -0
rem/api/mcp_router/resources.py
CHANGED
|
@@ -542,6 +542,227 @@ def register_status_resources(mcp: FastMCP):
|
|
|
542
542
|
"""
|
|
543
543
|
|
|
544
544
|
|
|
545
|
+
def register_session_resources(mcp: FastMCP):
|
|
546
|
+
"""
|
|
547
|
+
Register session resources for loading conversation history.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
mcp: FastMCP server instance
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
@mcp.resource("rem://sessions/{session_id}")
|
|
554
|
+
async def get_session_messages(session_id: str) -> str:
|
|
555
|
+
"""
|
|
556
|
+
Load a conversation session by ID.
|
|
557
|
+
|
|
558
|
+
Returns the full message history including user messages, assistant responses,
|
|
559
|
+
and tool calls. Useful for evaluators and analysis agents.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
session_id: Session UUID or identifier
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
Formatted conversation history as markdown string with:
|
|
566
|
+
- Message type (user/assistant/tool)
|
|
567
|
+
- Content
|
|
568
|
+
- Timestamps
|
|
569
|
+
- Tool call details (if any)
|
|
570
|
+
"""
|
|
571
|
+
from ...services.postgres import get_postgres_service
|
|
572
|
+
|
|
573
|
+
pg = get_postgres_service()
|
|
574
|
+
await pg.connect()
|
|
575
|
+
|
|
576
|
+
try:
|
|
577
|
+
# Query messages for session
|
|
578
|
+
query = """
|
|
579
|
+
SELECT id, message_type, content, metadata, created_at
|
|
580
|
+
FROM messages
|
|
581
|
+
WHERE session_id = $1
|
|
582
|
+
ORDER BY created_at ASC
|
|
583
|
+
"""
|
|
584
|
+
messages = await pg.fetch(query, session_id)
|
|
585
|
+
|
|
586
|
+
if not messages:
|
|
587
|
+
return f"# Session Not Found\n\nNo messages found for session_id: {session_id}"
|
|
588
|
+
|
|
589
|
+
# Format output
|
|
590
|
+
output = [f"# Session: {session_id}\n"]
|
|
591
|
+
output.append(f"**Total messages:** {len(messages)}\n")
|
|
592
|
+
|
|
593
|
+
for i, msg in enumerate(messages, 1):
|
|
594
|
+
msg_type = msg['message_type']
|
|
595
|
+
content = msg['content'] or "(empty)"
|
|
596
|
+
created = msg['created_at']
|
|
597
|
+
metadata = msg.get('metadata') or {}
|
|
598
|
+
|
|
599
|
+
# Format based on message type
|
|
600
|
+
if msg_type == 'user':
|
|
601
|
+
output.append(f"\n## [{i}] USER ({created})")
|
|
602
|
+
output.append(f"```\n{content[:1000]}{'...' if len(content) > 1000 else ''}\n```")
|
|
603
|
+
elif msg_type == 'assistant':
|
|
604
|
+
output.append(f"\n## [{i}] ASSISTANT ({created})")
|
|
605
|
+
output.append(f"```\n{content[:1000]}{'...' if len(content) > 1000 else ''}\n```")
|
|
606
|
+
elif msg_type == 'tool':
|
|
607
|
+
tool_name = metadata.get('tool_name', 'unknown')
|
|
608
|
+
output.append(f"\n## [{i}] TOOL: {tool_name} ({created})")
|
|
609
|
+
# Truncate tool results more aggressively
|
|
610
|
+
output.append(f"```json\n{content[:500]}{'...' if len(content) > 500 else ''}\n```")
|
|
611
|
+
else:
|
|
612
|
+
output.append(f"\n## [{i}] {msg_type.upper()} ({created})")
|
|
613
|
+
output.append(f"```\n{content[:500]}{'...' if len(content) > 500 else ''}\n```")
|
|
614
|
+
|
|
615
|
+
return "\n".join(output)
|
|
616
|
+
|
|
617
|
+
finally:
|
|
618
|
+
await pg.disconnect()
|
|
619
|
+
|
|
620
|
+
@mcp.resource("rem://sessions")
|
|
621
|
+
async def list_recent_sessions() -> str:
|
|
622
|
+
"""
|
|
623
|
+
List recent sessions with basic info.
|
|
624
|
+
|
|
625
|
+
Returns the most recent 20 sessions with:
|
|
626
|
+
- Session ID
|
|
627
|
+
- First user message (preview)
|
|
628
|
+
- Message count
|
|
629
|
+
- Timestamp
|
|
630
|
+
"""
|
|
631
|
+
from ...services.postgres import get_postgres_service
|
|
632
|
+
|
|
633
|
+
pg = get_postgres_service()
|
|
634
|
+
await pg.connect()
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
# Query recent sessions
|
|
638
|
+
query = """
|
|
639
|
+
SELECT
|
|
640
|
+
session_id,
|
|
641
|
+
MIN(created_at) as started_at,
|
|
642
|
+
COUNT(*) as message_count,
|
|
643
|
+
MIN(CASE WHEN message_type = 'user' THEN content END) as first_message
|
|
644
|
+
FROM messages
|
|
645
|
+
WHERE session_id IS NOT NULL
|
|
646
|
+
GROUP BY session_id
|
|
647
|
+
ORDER BY MIN(created_at) DESC
|
|
648
|
+
LIMIT 20
|
|
649
|
+
"""
|
|
650
|
+
sessions = await pg.fetch(query)
|
|
651
|
+
|
|
652
|
+
if not sessions:
|
|
653
|
+
return "# Recent Sessions\n\nNo sessions found."
|
|
654
|
+
|
|
655
|
+
output = ["# Recent Sessions\n"]
|
|
656
|
+
output.append(f"Showing {len(sessions)} most recent sessions:\n")
|
|
657
|
+
|
|
658
|
+
for session in sessions:
|
|
659
|
+
session_id = session['session_id']
|
|
660
|
+
started = session['started_at']
|
|
661
|
+
count = session['message_count']
|
|
662
|
+
first_msg = session['first_message'] or "(no user message)"
|
|
663
|
+
preview = first_msg[:80] + "..." if len(first_msg) > 80 else first_msg
|
|
664
|
+
|
|
665
|
+
output.append(f"\n## {session_id}")
|
|
666
|
+
output.append(f"- **Started:** {started}")
|
|
667
|
+
output.append(f"- **Messages:** {count}")
|
|
668
|
+
output.append(f"- **First message:** {preview}")
|
|
669
|
+
output.append(f"- **Load:** `rem://sessions/{session_id}`")
|
|
670
|
+
|
|
671
|
+
return "\n".join(output)
|
|
672
|
+
|
|
673
|
+
finally:
|
|
674
|
+
await pg.disconnect()
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def register_user_resources(mcp: FastMCP):
|
|
678
|
+
"""
|
|
679
|
+
Register user profile resources for on-demand profile loading.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
mcp: FastMCP server instance
|
|
683
|
+
"""
|
|
684
|
+
|
|
685
|
+
@mcp.resource("user://profile/{user_id}")
|
|
686
|
+
async def get_user_profile(user_id: str) -> str:
|
|
687
|
+
"""
|
|
688
|
+
Load a user's profile by ID.
|
|
689
|
+
|
|
690
|
+
Returns the user's profile information including:
|
|
691
|
+
- Email and name
|
|
692
|
+
- Summary (AI-generated profile summary)
|
|
693
|
+
- Interests and preferred topics
|
|
694
|
+
- Activity level
|
|
695
|
+
|
|
696
|
+
This resource is protected - each user can only access their own profile.
|
|
697
|
+
The user_id should match the authenticated user's ID from the JWT token.
|
|
698
|
+
|
|
699
|
+
Args:
|
|
700
|
+
user_id: User UUID from authentication
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
Formatted user profile as markdown string, or error if not found
|
|
704
|
+
"""
|
|
705
|
+
from ...services.postgres import get_postgres_service
|
|
706
|
+
from ...services.postgres.repository import Repository
|
|
707
|
+
from ...models.entities.user import User
|
|
708
|
+
|
|
709
|
+
pg = get_postgres_service()
|
|
710
|
+
await pg.connect()
|
|
711
|
+
|
|
712
|
+
try:
|
|
713
|
+
user_repo = Repository(User, "users", db=pg)
|
|
714
|
+
# Look up user by ID (user_id from JWT is the primary key)
|
|
715
|
+
user = await user_repo.get_by_id(user_id, tenant_id=None)
|
|
716
|
+
|
|
717
|
+
if not user:
|
|
718
|
+
return f"# User Profile Not Found\n\nNo user found with ID: {user_id}"
|
|
719
|
+
|
|
720
|
+
# Build profile output
|
|
721
|
+
output = [f"# User Profile: {user.name or user.email or 'Unknown'}"]
|
|
722
|
+
output.append("")
|
|
723
|
+
|
|
724
|
+
if user.email:
|
|
725
|
+
output.append(f"**Email:** {user.email}")
|
|
726
|
+
|
|
727
|
+
if user.role:
|
|
728
|
+
output.append(f"**Role:** {user.role}")
|
|
729
|
+
|
|
730
|
+
if user.tier:
|
|
731
|
+
output.append(f"**Tier:** {user.tier.value if hasattr(user.tier, 'value') else user.tier}")
|
|
732
|
+
|
|
733
|
+
if user.summary:
|
|
734
|
+
output.append(f"\n## Summary\n{user.summary}")
|
|
735
|
+
|
|
736
|
+
if user.interests:
|
|
737
|
+
output.append(f"\n## Interests\n- " + "\n- ".join(user.interests[:10]))
|
|
738
|
+
|
|
739
|
+
if user.preferred_topics:
|
|
740
|
+
output.append(f"\n## Preferred Topics\n- " + "\n- ".join(user.preferred_topics[:10]))
|
|
741
|
+
|
|
742
|
+
if user.activity_level:
|
|
743
|
+
output.append(f"\n**Activity Level:** {user.activity_level}")
|
|
744
|
+
|
|
745
|
+
if user.last_active_at:
|
|
746
|
+
output.append(f"**Last Active:** {user.last_active_at}")
|
|
747
|
+
|
|
748
|
+
# Add metadata if present (but redact sensitive fields)
|
|
749
|
+
if user.metadata:
|
|
750
|
+
safe_metadata = {k: v for k, v in user.metadata.items()
|
|
751
|
+
if k not in ('login_code', 'password', 'token', 'secret')}
|
|
752
|
+
if safe_metadata:
|
|
753
|
+
output.append(f"\n## Additional Info")
|
|
754
|
+
for key, value in list(safe_metadata.items())[:5]:
|
|
755
|
+
output.append(f"- **{key}:** {value}")
|
|
756
|
+
|
|
757
|
+
return "\n".join(output)
|
|
758
|
+
|
|
759
|
+
except Exception as e:
|
|
760
|
+
return f"# Error Loading Profile\n\nFailed to load user profile: {e}"
|
|
761
|
+
|
|
762
|
+
finally:
|
|
763
|
+
await pg.disconnect()
|
|
764
|
+
|
|
765
|
+
|
|
545
766
|
# Resource dispatcher for read_resource tool
|
|
546
767
|
async def load_resource(uri: str) -> dict | str:
|
|
547
768
|
"""
|
|
@@ -571,6 +792,8 @@ async def load_resource(uri: str) -> dict | str:
|
|
|
571
792
|
register_agent_resources(mcp)
|
|
572
793
|
register_file_resources(mcp)
|
|
573
794
|
register_status_resources(mcp)
|
|
795
|
+
register_session_resources(mcp)
|
|
796
|
+
register_user_resources(mcp)
|
|
574
797
|
|
|
575
798
|
# 1. Try exact match in regular resources
|
|
576
799
|
resources = await mcp.get_resources()
|
rem/api/mcp_router/server.py
CHANGED
|
@@ -34,6 +34,7 @@ from .resources import (
|
|
|
34
34
|
register_status_resources,
|
|
35
35
|
)
|
|
36
36
|
from .tools import (
|
|
37
|
+
ask_agent,
|
|
37
38
|
ask_rem_agent,
|
|
38
39
|
get_schema,
|
|
39
40
|
ingest_into_rem,
|
|
@@ -203,6 +204,9 @@ def create_mcp_server(is_local: bool = False) -> FastMCP:
|
|
|
203
204
|
mcp.tool()(get_schema)
|
|
204
205
|
mcp.tool()(save_agent)
|
|
205
206
|
|
|
207
|
+
# Register multi-agent tools
|
|
208
|
+
mcp.tool()(ask_agent)
|
|
209
|
+
|
|
206
210
|
# Register test tool only in development environment (not staging/production)
|
|
207
211
|
if settings.environment not in ("staging", "production"):
|
|
208
212
|
mcp.tool()(test_error_handling)
|