remdb 0.3.14__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.
- 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 +502 -0
- rem/agentic/context.py +51 -27
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/tool_wrapper.py +112 -17
- rem/agentic/otel/setup.py +93 -4
- rem/agentic/providers/phoenix.py +302 -109
- rem/agentic/providers/pydantic_ai.py +215 -26
- rem/agentic/schema.py +361 -21
- rem/agentic/tools/rem_tools.py +3 -3
- rem/api/README.md +215 -1
- rem/api/deps.py +255 -0
- rem/api/main.py +132 -40
- rem/api/mcp_router/resources.py +1 -1
- rem/api/mcp_router/server.py +26 -5
- rem/api/mcp_router/tools.py +465 -7
- rem/api/routers/admin.py +494 -0
- rem/api/routers/auth.py +70 -0
- rem/api/routers/chat/completions.py +402 -20
- rem/api/routers/chat/models.py +88 -10
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +542 -0
- rem/api/routers/chat/streaming.py +642 -45
- rem/api/routers/dev.py +81 -0
- rem/api/routers/feedback.py +268 -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/cluster.py +1808 -0
- rem/cli/commands/configure.py +1 -3
- rem/cli/commands/db.py +386 -143
- rem/cli/commands/experiments.py +418 -27
- rem/cli/commands/process.py +14 -8
- rem/cli/commands/schema.py +97 -50
- rem/cli/main.py +27 -6
- rem/config.py +10 -3
- rem/models/core/core_model.py +7 -1
- rem/models/core/experiment.py +54 -0
- 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/registry.py +10 -4
- rem/schemas/agents/rem.yaml +7 -3
- rem/services/content/service.py +92 -20
- rem/services/embeddings/api.py +4 -4
- rem/services/embeddings/worker.py +16 -16
- rem/services/phoenix/client.py +154 -14
- 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 +205 -4
- rem/services/postgres/service.py +6 -6
- rem/services/rem/parser.py +44 -9
- rem/services/rem/service.py +36 -2
- rem/services/session/compression.py +24 -1
- rem/services/session/reload.py +1 -1
- rem/settings.py +324 -23
- 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/date_utils.py +2 -2
- rem/utils/model_helpers.py +156 -1
- rem/utils/schema_loader.py +220 -22
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +3 -1
- rem/workers/__init__.py +3 -1
- rem/workers/db_listener.py +579 -0
- rem/workers/unlogged_maintainer.py +463 -0
- {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/METADATA +335 -226
- {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/RECORD +86 -66
- {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/WHEEL +1 -1
- rem/sql/002_install_models.sql +0 -1068
- rem/sql/install_models.sql +0 -1051
- rem/sql/migrations/003_seed_default_user.sql +0 -48
- {remdb-0.3.14.dist-info → remdb-0.3.133.dist-info}/entry_points.txt +0 -0
rem/agentic/README.md
CHANGED
|
@@ -640,6 +640,82 @@ If `query_agent_model` is not set, the agent uses `settings.llm.default_model`.
|
|
|
640
640
|
- PostgreSQL dialect aware (knows about KV_STORE, embeddings tables)
|
|
641
641
|
- Can generate multi-step query plans for complex questions
|
|
642
642
|
|
|
643
|
+
## SSE Simulator
|
|
644
|
+
|
|
645
|
+
The SSE Simulator is a **programmatic** event generator (not an LLM-based agent) that produces
|
|
646
|
+
a scripted sequence of SSE events for testing and demonstrating the streaming protocol.
|
|
647
|
+
|
|
648
|
+
### Purpose
|
|
649
|
+
|
|
650
|
+
- Frontend development without LLM costs
|
|
651
|
+
- Testing SSE parsing and rendering
|
|
652
|
+
- Demonstrating the full event protocol
|
|
653
|
+
- Load testing streaming infrastructure
|
|
654
|
+
|
|
655
|
+
### Usage
|
|
656
|
+
|
|
657
|
+
```python
|
|
658
|
+
from rem.agentic.agents import stream_simulator_events
|
|
659
|
+
from rem.api.routers.chat.sse_events import format_sse_event
|
|
660
|
+
|
|
661
|
+
# Generate all event types
|
|
662
|
+
async for event in stream_simulator_events("demo"):
|
|
663
|
+
print(format_sse_event(event))
|
|
664
|
+
|
|
665
|
+
# Minimal demo (text + done only)
|
|
666
|
+
from rem.agentic.agents import stream_minimal_demo
|
|
667
|
+
async for event in stream_minimal_demo("Hello world!"):
|
|
668
|
+
print(event)
|
|
669
|
+
|
|
670
|
+
# Error simulation
|
|
671
|
+
from rem.agentic.agents import stream_error_demo
|
|
672
|
+
async for event in stream_error_demo(error_after_words=10):
|
|
673
|
+
print(event)
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### Event Sequence
|
|
677
|
+
|
|
678
|
+
The full simulator produces events in this order:
|
|
679
|
+
|
|
680
|
+
1. **Reasoning** (4 steps) - Model thinking process
|
|
681
|
+
2. **Progress** (step 1/4) - Starting
|
|
682
|
+
3. **Tool calls** (2 tools) - Simulated tool invocations
|
|
683
|
+
4. **Progress** (step 2/4) - Generating
|
|
684
|
+
5. **Text deltas** - Streamed markdown content
|
|
685
|
+
6. **Progress** (step 3/4) - Formatting
|
|
686
|
+
7. **Metadata** - Confidence, sources, flags
|
|
687
|
+
8. **Progress** (step 4/4) - Preparing actions
|
|
688
|
+
9. **Action request** - Feedback card with buttons and inputs
|
|
689
|
+
10. **Progress** (all complete)
|
|
690
|
+
11. **Done** - Stream completion
|
|
691
|
+
|
|
692
|
+
### Configuration Options
|
|
693
|
+
|
|
694
|
+
```python
|
|
695
|
+
await stream_simulator_events(
|
|
696
|
+
prompt="demo",
|
|
697
|
+
delay_ms=50, # Delay between events
|
|
698
|
+
include_reasoning=True, # Emit reasoning events
|
|
699
|
+
include_progress=True, # Emit progress events
|
|
700
|
+
include_tool_calls=True, # Emit tool call events
|
|
701
|
+
include_actions=True, # Emit action request at end
|
|
702
|
+
include_metadata=True, # Emit metadata event
|
|
703
|
+
)
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### HTTP Endpoint
|
|
707
|
+
|
|
708
|
+
Use the simulator via the chat completions endpoint:
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
curl -X POST http://localhost:8000/api/v1/chat/completions \
|
|
712
|
+
-H "Content-Type: application/json" \
|
|
713
|
+
-H "X-Agent-Schema: simulator" \
|
|
714
|
+
-d '{"messages": [{"role": "user", "content": "demo"}], "stream": true}'
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
See `rem/api/README.md` for full SSE event protocol documentation.
|
|
718
|
+
|
|
643
719
|
## Future Work
|
|
644
720
|
|
|
645
721
|
- [ ] Phoenix evaluator integration
|
rem/agentic/__init__.py
CHANGED
|
@@ -17,6 +17,14 @@ from .schema import (
|
|
|
17
17
|
)
|
|
18
18
|
from .providers.pydantic_ai import create_agent_from_schema_file, create_agent, AgentRuntime
|
|
19
19
|
from .query_helper import ask_rem, REMQueryOutput
|
|
20
|
+
from .llm_provider_models import (
|
|
21
|
+
ModelInfo,
|
|
22
|
+
AVAILABLE_MODELS,
|
|
23
|
+
ALLOWED_MODEL_IDS,
|
|
24
|
+
is_valid_model,
|
|
25
|
+
get_valid_model_or_default,
|
|
26
|
+
get_model_by_id,
|
|
27
|
+
)
|
|
20
28
|
|
|
21
29
|
__all__ = [
|
|
22
30
|
# Context and Query
|
|
@@ -36,4 +44,11 @@ __all__ = [
|
|
|
36
44
|
# REM Query Helpers
|
|
37
45
|
"ask_rem",
|
|
38
46
|
"REMQueryOutput",
|
|
47
|
+
# LLM Provider Models
|
|
48
|
+
"ModelInfo",
|
|
49
|
+
"AVAILABLE_MODELS",
|
|
50
|
+
"ALLOWED_MODEL_IDS",
|
|
51
|
+
"is_valid_model",
|
|
52
|
+
"get_valid_model_or_default",
|
|
53
|
+
"get_model_by_id",
|
|
39
54
|
]
|
rem/agentic/agents/__init__.py
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
"""
|
|
2
2
|
REM Agents - Specialized agents for REM operations.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Most agents are defined as YAML schemas in src/rem/schemas/agents/.
|
|
5
5
|
Use create_agent_from_schema_file() to instantiate agents.
|
|
6
|
+
|
|
7
|
+
The SSE Simulator is a special programmatic "agent" that generates
|
|
8
|
+
scripted SSE events for testing and demonstration - it doesn't use an LLM.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
from .sse_simulator import (
|
|
12
|
+
stream_simulator_events,
|
|
13
|
+
stream_minimal_demo,
|
|
14
|
+
stream_error_demo,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# SSE Simulator (programmatic, no LLM)
|
|
19
|
+
"stream_simulator_events",
|
|
20
|
+
"stream_minimal_demo",
|
|
21
|
+
"stream_error_demo",
|
|
22
|
+
]
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SSE Event Simulator Agent.
|
|
3
|
+
|
|
4
|
+
A programmatic simulator that generates rich SSE events for testing and
|
|
5
|
+
demonstrating the streaming protocol. NOT an LLM-based agent - this is
|
|
6
|
+
pure Python that emits scripted SSE events.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from rem.agentic.agents.simulator import stream_simulator_events
|
|
10
|
+
|
|
11
|
+
async for event in stream_simulator_events("demo"):
|
|
12
|
+
yield format_sse_event(event)
|
|
13
|
+
|
|
14
|
+
The simulator demonstrates:
|
|
15
|
+
1. Reasoning events (thinking process)
|
|
16
|
+
2. Text deltas (streamed content)
|
|
17
|
+
3. Progress indicators
|
|
18
|
+
4. Tool call events
|
|
19
|
+
5. Action solicitations (user interaction)
|
|
20
|
+
6. Metadata events
|
|
21
|
+
7. Done event
|
|
22
|
+
|
|
23
|
+
This is useful for:
|
|
24
|
+
- Frontend development without LLM costs
|
|
25
|
+
- Testing SSE parsing and rendering
|
|
26
|
+
- Demonstrating the full event protocol
|
|
27
|
+
- Load testing streaming infrastructure
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import asyncio
|
|
31
|
+
import time
|
|
32
|
+
import uuid
|
|
33
|
+
from typing import AsyncGenerator
|
|
34
|
+
|
|
35
|
+
from rem.api.routers.chat.sse_events import (
|
|
36
|
+
ReasoningEvent,
|
|
37
|
+
ActionRequestEvent,
|
|
38
|
+
MetadataEvent,
|
|
39
|
+
ProgressEvent,
|
|
40
|
+
ToolCallEvent,
|
|
41
|
+
DoneEvent,
|
|
42
|
+
ActionRequestCard,
|
|
43
|
+
ActionSubmit,
|
|
44
|
+
ActionStyle,
|
|
45
|
+
InputText,
|
|
46
|
+
InputChoiceSet,
|
|
47
|
+
ActionDisplayStyle,
|
|
48
|
+
format_sse_event,
|
|
49
|
+
)
|
|
50
|
+
from rem.api.routers.chat.models import (
|
|
51
|
+
ChatCompletionStreamResponse,
|
|
52
|
+
ChatCompletionStreamChoice,
|
|
53
|
+
ChatCompletionMessageDelta,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# =============================================================================
|
|
58
|
+
# Demo Content
|
|
59
|
+
# =============================================================================
|
|
60
|
+
|
|
61
|
+
DEMO_REASONING_STEPS = [
|
|
62
|
+
"Analyzing the user's request...",
|
|
63
|
+
"Considering the best approach to demonstrate SSE events...",
|
|
64
|
+
"Planning a response that showcases all event types...",
|
|
65
|
+
"Preparing rich markdown content with examples...",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
DEMO_MARKDOWN_CONTENT = """# SSE Streaming Demo
|
|
69
|
+
|
|
70
|
+
This response demonstrates the **rich SSE event protocol** with multiple event types streamed in real-time.
|
|
71
|
+
|
|
72
|
+
## What You're Seeing
|
|
73
|
+
|
|
74
|
+
1. **Reasoning Events** - The "thinking" process shown in a collapsible section
|
|
75
|
+
2. **Text Streaming** - This markdown content, streamed word by word
|
|
76
|
+
3. **Progress Events** - Step indicators during processing
|
|
77
|
+
4. **Tool Calls** - Simulated tool invocations
|
|
78
|
+
5. **Action Requests** - Interactive UI elements for user input
|
|
79
|
+
|
|
80
|
+
## Code Example
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from rem.agentic.agents.simulator import stream_simulator_events
|
|
84
|
+
|
|
85
|
+
async def demo():
|
|
86
|
+
async for event in stream_simulator_events("demo"):
|
|
87
|
+
print(event.type, event)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Features Table
|
|
91
|
+
|
|
92
|
+
| Event Type | Purpose | UI Display |
|
|
93
|
+
|------------|---------|------------|
|
|
94
|
+
| `reasoning` | Model thinking | Collapsible section |
|
|
95
|
+
| `text_delta` | Content chunks | Main response area |
|
|
96
|
+
| `progress` | Step indicators | Progress bar |
|
|
97
|
+
| `tool_call` | Tool invocations | Tool status panel |
|
|
98
|
+
| `action_request` | User input | Buttons/forms |
|
|
99
|
+
| `metadata` | System info | Hidden or badge |
|
|
100
|
+
|
|
101
|
+
## Summary
|
|
102
|
+
|
|
103
|
+
The SSE protocol enables rich, interactive AI experiences beyond simple text streaming. Each event type serves a specific purpose in the UI.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
DEMO_TOOL_CALLS = [
|
|
108
|
+
("search_knowledge", {"query": "SSE streaming best practices"}),
|
|
109
|
+
("format_response", {"style": "markdown", "include_examples": True}),
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
DEMO_PROGRESS_STEPS = [
|
|
113
|
+
"Initializing response",
|
|
114
|
+
"Generating content",
|
|
115
|
+
"Formatting output",
|
|
116
|
+
"Preparing actions",
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# =============================================================================
|
|
121
|
+
# Simulator Functions
|
|
122
|
+
# =============================================================================
|
|
123
|
+
|
|
124
|
+
async def stream_simulator_events(
|
|
125
|
+
prompt: str,
|
|
126
|
+
delay_ms: int = 50,
|
|
127
|
+
include_reasoning: bool = True,
|
|
128
|
+
include_progress: bool = True,
|
|
129
|
+
include_tool_calls: bool = True,
|
|
130
|
+
include_actions: bool = True,
|
|
131
|
+
include_metadata: bool = True,
|
|
132
|
+
# Message correlation IDs
|
|
133
|
+
message_id: str | None = None,
|
|
134
|
+
in_reply_to: str | None = None,
|
|
135
|
+
session_id: str | None = None,
|
|
136
|
+
# Model info
|
|
137
|
+
model: str = "simulator-v1.0.0",
|
|
138
|
+
) -> AsyncGenerator[str, None]:
|
|
139
|
+
"""
|
|
140
|
+
Generate a sequence of SSE events simulating an AI response.
|
|
141
|
+
|
|
142
|
+
This is a programmatic simulator - no LLM calls are made.
|
|
143
|
+
Events are yielded in a realistic order with configurable delays.
|
|
144
|
+
|
|
145
|
+
Text content uses OpenAI-compatible format for consistency with real agents.
|
|
146
|
+
Other events (reasoning, progress, tool_call, metadata) use named SSE events.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
prompt: User prompt (used to vary output slightly)
|
|
150
|
+
delay_ms: Delay between events in milliseconds
|
|
151
|
+
include_reasoning: Whether to emit reasoning events
|
|
152
|
+
include_progress: Whether to emit progress events
|
|
153
|
+
include_tool_calls: Whether to emit tool call events
|
|
154
|
+
include_actions: Whether to emit action request at end
|
|
155
|
+
include_metadata: Whether to emit metadata event
|
|
156
|
+
message_id: Database ID of the assistant message being streamed
|
|
157
|
+
in_reply_to: Database ID of the user message this responds to
|
|
158
|
+
session_id: Session ID for conversation correlation
|
|
159
|
+
model: Model name for response metadata
|
|
160
|
+
|
|
161
|
+
Yields:
|
|
162
|
+
SSE-formatted strings ready for HTTP streaming
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
```python
|
|
166
|
+
async for sse_string in stream_simulator_events("demo"):
|
|
167
|
+
print(sse_string)
|
|
168
|
+
```
|
|
169
|
+
"""
|
|
170
|
+
delay = delay_ms / 1000.0
|
|
171
|
+
request_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
|
|
172
|
+
created_at = int(time.time())
|
|
173
|
+
is_first_chunk = True
|
|
174
|
+
|
|
175
|
+
# Phase 1: Reasoning events
|
|
176
|
+
if include_reasoning:
|
|
177
|
+
for i, step in enumerate(DEMO_REASONING_STEPS):
|
|
178
|
+
await asyncio.sleep(delay)
|
|
179
|
+
yield format_sse_event(ReasoningEvent(content=step + "\n", step=i + 1))
|
|
180
|
+
|
|
181
|
+
# Phase 2: Progress - Starting
|
|
182
|
+
if include_progress:
|
|
183
|
+
await asyncio.sleep(delay)
|
|
184
|
+
yield format_sse_event(ProgressEvent(
|
|
185
|
+
step=1,
|
|
186
|
+
total_steps=len(DEMO_PROGRESS_STEPS),
|
|
187
|
+
label=DEMO_PROGRESS_STEPS[0],
|
|
188
|
+
status="in_progress"
|
|
189
|
+
))
|
|
190
|
+
|
|
191
|
+
# Phase 3: Tool calls
|
|
192
|
+
if include_tool_calls:
|
|
193
|
+
for tool_name, args in DEMO_TOOL_CALLS:
|
|
194
|
+
tool_id = f"call_{uuid.uuid4().hex[:8]}"
|
|
195
|
+
|
|
196
|
+
await asyncio.sleep(delay)
|
|
197
|
+
yield format_sse_event(ToolCallEvent(
|
|
198
|
+
tool_name=tool_name,
|
|
199
|
+
tool_id=tool_id,
|
|
200
|
+
status="started",
|
|
201
|
+
arguments=args
|
|
202
|
+
))
|
|
203
|
+
|
|
204
|
+
await asyncio.sleep(delay * 3) # Simulate tool execution
|
|
205
|
+
yield format_sse_event(ToolCallEvent(
|
|
206
|
+
tool_name=tool_name,
|
|
207
|
+
tool_id=tool_id,
|
|
208
|
+
status="completed",
|
|
209
|
+
result=f"Retrieved data for {tool_name}"
|
|
210
|
+
))
|
|
211
|
+
|
|
212
|
+
# Phase 4: Progress - Generating
|
|
213
|
+
if include_progress:
|
|
214
|
+
await asyncio.sleep(delay)
|
|
215
|
+
yield format_sse_event(ProgressEvent(
|
|
216
|
+
step=2,
|
|
217
|
+
total_steps=len(DEMO_PROGRESS_STEPS),
|
|
218
|
+
label=DEMO_PROGRESS_STEPS[1],
|
|
219
|
+
status="in_progress"
|
|
220
|
+
))
|
|
221
|
+
|
|
222
|
+
# Phase 5: Stream text content in OpenAI format
|
|
223
|
+
words = DEMO_MARKDOWN_CONTENT.split(" ")
|
|
224
|
+
buffer = ""
|
|
225
|
+
for i, word in enumerate(words):
|
|
226
|
+
buffer += word + " "
|
|
227
|
+
# Emit every few words to simulate realistic streaming
|
|
228
|
+
if len(buffer) > 20 or i == len(words) - 1:
|
|
229
|
+
await asyncio.sleep(delay)
|
|
230
|
+
# OpenAI-compatible format
|
|
231
|
+
chunk = ChatCompletionStreamResponse(
|
|
232
|
+
id=request_id,
|
|
233
|
+
created=created_at,
|
|
234
|
+
model=model,
|
|
235
|
+
choices=[
|
|
236
|
+
ChatCompletionStreamChoice(
|
|
237
|
+
index=0,
|
|
238
|
+
delta=ChatCompletionMessageDelta(
|
|
239
|
+
role="assistant" if is_first_chunk else None,
|
|
240
|
+
content=buffer,
|
|
241
|
+
),
|
|
242
|
+
finish_reason=None,
|
|
243
|
+
)
|
|
244
|
+
],
|
|
245
|
+
)
|
|
246
|
+
is_first_chunk = False
|
|
247
|
+
yield f"data: {chunk.model_dump_json()}\n\n"
|
|
248
|
+
buffer = ""
|
|
249
|
+
|
|
250
|
+
# Phase 6: Progress - Formatting
|
|
251
|
+
if include_progress:
|
|
252
|
+
await asyncio.sleep(delay)
|
|
253
|
+
yield format_sse_event(ProgressEvent(
|
|
254
|
+
step=3,
|
|
255
|
+
total_steps=len(DEMO_PROGRESS_STEPS),
|
|
256
|
+
label=DEMO_PROGRESS_STEPS[2],
|
|
257
|
+
status="in_progress"
|
|
258
|
+
))
|
|
259
|
+
|
|
260
|
+
# Phase 7: Metadata (includes message correlation IDs)
|
|
261
|
+
if include_metadata:
|
|
262
|
+
await asyncio.sleep(delay)
|
|
263
|
+
yield format_sse_event(MetadataEvent(
|
|
264
|
+
# Message correlation IDs
|
|
265
|
+
message_id=message_id,
|
|
266
|
+
in_reply_to=in_reply_to,
|
|
267
|
+
session_id=session_id,
|
|
268
|
+
# Session info
|
|
269
|
+
session_name="SSE Demo Session",
|
|
270
|
+
# Quality indicators
|
|
271
|
+
confidence=0.95,
|
|
272
|
+
sources=["rem/api/routers/chat/sse_events.py", "rem/agentic/agents/sse_simulator.py"],
|
|
273
|
+
# Model info
|
|
274
|
+
model_version=model,
|
|
275
|
+
# Performance metrics
|
|
276
|
+
latency_ms=int(len(words) * delay_ms),
|
|
277
|
+
token_count=len(words),
|
|
278
|
+
# System flags
|
|
279
|
+
flags=["demo_mode"],
|
|
280
|
+
hidden=False,
|
|
281
|
+
extra={"prompt_length": len(prompt)}
|
|
282
|
+
))
|
|
283
|
+
|
|
284
|
+
# Phase 8: Progress - Preparing actions
|
|
285
|
+
if include_progress:
|
|
286
|
+
await asyncio.sleep(delay)
|
|
287
|
+
yield format_sse_event(ProgressEvent(
|
|
288
|
+
step=4,
|
|
289
|
+
total_steps=len(DEMO_PROGRESS_STEPS),
|
|
290
|
+
label=DEMO_PROGRESS_STEPS[3],
|
|
291
|
+
status="in_progress"
|
|
292
|
+
))
|
|
293
|
+
|
|
294
|
+
# Phase 9: Action solicitation
|
|
295
|
+
if include_actions:
|
|
296
|
+
await asyncio.sleep(delay)
|
|
297
|
+
yield format_sse_event(ActionRequestEvent(
|
|
298
|
+
card=ActionRequestCard(
|
|
299
|
+
id=f"feedback-{uuid.uuid4().hex[:8]}",
|
|
300
|
+
prompt="Was this SSE demonstration helpful?",
|
|
301
|
+
display_style=ActionDisplayStyle.INLINE,
|
|
302
|
+
actions=[
|
|
303
|
+
ActionSubmit(
|
|
304
|
+
id="helpful-yes",
|
|
305
|
+
title="Yes, very helpful!",
|
|
306
|
+
style=ActionStyle.POSITIVE,
|
|
307
|
+
data={"rating": 5, "feedback": "positive"}
|
|
308
|
+
),
|
|
309
|
+
ActionSubmit(
|
|
310
|
+
id="helpful-somewhat",
|
|
311
|
+
title="Somewhat",
|
|
312
|
+
style=ActionStyle.DEFAULT,
|
|
313
|
+
data={"rating": 3, "feedback": "neutral"}
|
|
314
|
+
),
|
|
315
|
+
ActionSubmit(
|
|
316
|
+
id="helpful-no",
|
|
317
|
+
title="Not really",
|
|
318
|
+
style=ActionStyle.SECONDARY,
|
|
319
|
+
data={"rating": 1, "feedback": "negative"}
|
|
320
|
+
),
|
|
321
|
+
],
|
|
322
|
+
inputs=[
|
|
323
|
+
InputText(
|
|
324
|
+
id="comments",
|
|
325
|
+
label="Any comments?",
|
|
326
|
+
placeholder="Optional feedback...",
|
|
327
|
+
is_multiline=True,
|
|
328
|
+
max_length=500
|
|
329
|
+
),
|
|
330
|
+
InputChoiceSet(
|
|
331
|
+
id="use_case",
|
|
332
|
+
label="What's your use case?",
|
|
333
|
+
choices=[
|
|
334
|
+
{"title": "Frontend development", "value": "frontend"},
|
|
335
|
+
{"title": "Testing", "value": "testing"},
|
|
336
|
+
{"title": "Learning", "value": "learning"},
|
|
337
|
+
{"title": "Other", "value": "other"},
|
|
338
|
+
],
|
|
339
|
+
is_required=False
|
|
340
|
+
),
|
|
341
|
+
],
|
|
342
|
+
timeout_ms=60000,
|
|
343
|
+
fallback_text="Please provide feedback on this demo."
|
|
344
|
+
)
|
|
345
|
+
))
|
|
346
|
+
|
|
347
|
+
# Phase 10: Mark all progress complete
|
|
348
|
+
if include_progress:
|
|
349
|
+
for i, label in enumerate(DEMO_PROGRESS_STEPS):
|
|
350
|
+
await asyncio.sleep(delay / 2)
|
|
351
|
+
yield format_sse_event(ProgressEvent(
|
|
352
|
+
step=i + 1,
|
|
353
|
+
total_steps=len(DEMO_PROGRESS_STEPS),
|
|
354
|
+
label=label,
|
|
355
|
+
status="completed"
|
|
356
|
+
))
|
|
357
|
+
|
|
358
|
+
# Phase 11: Final chunk with finish_reason
|
|
359
|
+
final_chunk = ChatCompletionStreamResponse(
|
|
360
|
+
id=request_id,
|
|
361
|
+
created=created_at,
|
|
362
|
+
model=model,
|
|
363
|
+
choices=[
|
|
364
|
+
ChatCompletionStreamChoice(
|
|
365
|
+
index=0,
|
|
366
|
+
delta=ChatCompletionMessageDelta(),
|
|
367
|
+
finish_reason="stop",
|
|
368
|
+
)
|
|
369
|
+
],
|
|
370
|
+
)
|
|
371
|
+
yield f"data: {final_chunk.model_dump_json()}\n\n"
|
|
372
|
+
|
|
373
|
+
# Phase 12: Done event
|
|
374
|
+
await asyncio.sleep(delay)
|
|
375
|
+
yield format_sse_event(DoneEvent(reason="stop"))
|
|
376
|
+
|
|
377
|
+
# Phase 13: OpenAI termination marker
|
|
378
|
+
yield "data: [DONE]\n\n"
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
async def stream_minimal_demo(
|
|
382
|
+
content: str = "Hello from the simulator!",
|
|
383
|
+
delay_ms: int = 30,
|
|
384
|
+
model: str = "simulator-v1.0.0",
|
|
385
|
+
) -> AsyncGenerator[str, None]:
|
|
386
|
+
"""
|
|
387
|
+
Generate a minimal SSE sequence with just text and done.
|
|
388
|
+
|
|
389
|
+
Useful for simple testing without all event types.
|
|
390
|
+
Uses OpenAI-compatible format for text content.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
content: Text content to stream
|
|
394
|
+
delay_ms: Delay between chunks
|
|
395
|
+
model: Model name for response metadata
|
|
396
|
+
|
|
397
|
+
Yields:
|
|
398
|
+
SSE-formatted strings
|
|
399
|
+
"""
|
|
400
|
+
delay = delay_ms / 1000.0
|
|
401
|
+
request_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
|
|
402
|
+
created_at = int(time.time())
|
|
403
|
+
is_first_chunk = True
|
|
404
|
+
|
|
405
|
+
# Stream content word by word in OpenAI format
|
|
406
|
+
words = content.split(" ")
|
|
407
|
+
for word in words:
|
|
408
|
+
await asyncio.sleep(delay)
|
|
409
|
+
chunk = ChatCompletionStreamResponse(
|
|
410
|
+
id=request_id,
|
|
411
|
+
created=created_at,
|
|
412
|
+
model=model,
|
|
413
|
+
choices=[
|
|
414
|
+
ChatCompletionStreamChoice(
|
|
415
|
+
index=0,
|
|
416
|
+
delta=ChatCompletionMessageDelta(
|
|
417
|
+
role="assistant" if is_first_chunk else None,
|
|
418
|
+
content=word + " ",
|
|
419
|
+
),
|
|
420
|
+
finish_reason=None,
|
|
421
|
+
)
|
|
422
|
+
],
|
|
423
|
+
)
|
|
424
|
+
is_first_chunk = False
|
|
425
|
+
yield f"data: {chunk.model_dump_json()}\n\n"
|
|
426
|
+
|
|
427
|
+
# Final chunk with finish_reason
|
|
428
|
+
final_chunk = ChatCompletionStreamResponse(
|
|
429
|
+
id=request_id,
|
|
430
|
+
created=created_at,
|
|
431
|
+
model=model,
|
|
432
|
+
choices=[
|
|
433
|
+
ChatCompletionStreamChoice(
|
|
434
|
+
index=0,
|
|
435
|
+
delta=ChatCompletionMessageDelta(),
|
|
436
|
+
finish_reason="stop",
|
|
437
|
+
)
|
|
438
|
+
],
|
|
439
|
+
)
|
|
440
|
+
yield f"data: {final_chunk.model_dump_json()}\n\n"
|
|
441
|
+
|
|
442
|
+
await asyncio.sleep(delay)
|
|
443
|
+
yield format_sse_event(DoneEvent(reason="stop"))
|
|
444
|
+
yield "data: [DONE]\n\n"
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
async def stream_error_demo(
|
|
448
|
+
error_after_words: int = 10,
|
|
449
|
+
model: str = "simulator-v1.0.0",
|
|
450
|
+
) -> AsyncGenerator[str, None]:
|
|
451
|
+
"""
|
|
452
|
+
Generate an SSE sequence that ends with an error.
|
|
453
|
+
|
|
454
|
+
Useful for testing error handling in the frontend.
|
|
455
|
+
Uses OpenAI-compatible format for text content.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
error_after_words: Number of words before error
|
|
459
|
+
model: Model name for response metadata
|
|
460
|
+
|
|
461
|
+
Yields:
|
|
462
|
+
SSE-formatted strings including an error event
|
|
463
|
+
"""
|
|
464
|
+
from rem.api.routers.chat.sse_events import ErrorEvent
|
|
465
|
+
|
|
466
|
+
request_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
|
|
467
|
+
created_at = int(time.time())
|
|
468
|
+
is_first_chunk = True
|
|
469
|
+
|
|
470
|
+
content = "This is a demo that will encounter an error during streaming. Watch what happens when things go wrong..."
|
|
471
|
+
words = content.split(" ")
|
|
472
|
+
|
|
473
|
+
for i, word in enumerate(words[:error_after_words]):
|
|
474
|
+
await asyncio.sleep(0.03)
|
|
475
|
+
chunk = ChatCompletionStreamResponse(
|
|
476
|
+
id=request_id,
|
|
477
|
+
created=created_at,
|
|
478
|
+
model=model,
|
|
479
|
+
choices=[
|
|
480
|
+
ChatCompletionStreamChoice(
|
|
481
|
+
index=0,
|
|
482
|
+
delta=ChatCompletionMessageDelta(
|
|
483
|
+
role="assistant" if is_first_chunk else None,
|
|
484
|
+
content=word + " ",
|
|
485
|
+
),
|
|
486
|
+
finish_reason=None,
|
|
487
|
+
)
|
|
488
|
+
],
|
|
489
|
+
)
|
|
490
|
+
is_first_chunk = False
|
|
491
|
+
yield f"data: {chunk.model_dump_json()}\n\n"
|
|
492
|
+
|
|
493
|
+
await asyncio.sleep(0.1)
|
|
494
|
+
yield format_sse_event(ErrorEvent(
|
|
495
|
+
code="simulated_error",
|
|
496
|
+
message="This is a simulated error for testing purposes",
|
|
497
|
+
details={"words_sent": error_after_words, "demo": True},
|
|
498
|
+
recoverable=True
|
|
499
|
+
))
|
|
500
|
+
|
|
501
|
+
yield format_sse_event(DoneEvent(reason="error"))
|
|
502
|
+
yield "data: [DONE]\n\n"
|