Refactor Cloud AI chat stream into scoped execution services
Problem
Refactor apps/cloud/apps/ai/views.py chat_stream into smaller scope-resolution, tool-execution, and SSE-streaming units. Introduce an explicit execution context object so auth, project selection, context assembly, and async bridging are no longer coupled inside one controller function.
Context
chat_stream in apps/cloud/apps/ai/views.py (lines 118–241) is a single 120-line view that mixes:
- Auth/membership check
- JSON request parsing
- AI service availability check
- Scope resolution (project vs org) with different context assembly
- Tool executor closure construction
- Async-to-sync SSE bridging with event-loop management
Existing tests in tests/cloud/test_chat_stream.py are source-code inspection tests that read views.py and assert specific strings. Any refactor must keep extracted code in views.py to avoid breaking those tests.
Key files: apps/cloud/apps/ai/views.py, apps/cloud/apps/ai/service.py, apps/cloud/apps/ai/urls.py, apps/cloud/apps/ai/org_urls.py.
Possible Solutions
-
Extract to separate service module — Move scope resolution and SSE generation to a new
chat_service.py. Trade-off: breaks 10+ existing source-code-inspection tests that readviews.py. -
Recommended: Extract helpers within views.py — Create a
ChatExecutionContextdataclass, aresolve_chat_scope()function, and agenerate_chat_sse()generator in the same file.chat_streambecomes a thin controller composing these. Preserves all existing tests since strings stay inviews.py. Minimal abstraction, no new files. -
Class-based view — Convert to a CBV with methods per concern. Trade-off: over-engineered for one endpoint, breaks existing test assertions on
def chat_stream(.
Plan
Approach #2. Files to modify: apps/cloud/apps/ai/views.py. New test file: tests/cloud/test_chat_stream_refactor.py.
Steps:
1. Write failing tests for new structure (ChatExecutionContext, resolve_chat_scope, generate_chat_sse)
2. Add ChatExecutionContext dataclass
3. Extract resolve_chat_scope() — scope validation, project lookup, context assembly
4. Extract generate_chat_sse() — async bridge, event loop, SSE formatting
5. Slim chat_stream to compose these three pieces
6. Run existing + new tests to confirm all pass
Implementation Progress
Key decisions
- Kept all extracted code in
views.pyto preserve 49 existing source-code-inspection tests. - Used a plain
@dataclass(ChatExecutionContext) — no Pydantic, no base class. Just fields. resolve_chat_scopereturnsChatExecutionContext | JsonResponse— union return avoids exception-based control flow.generate_chat_sseis a standalone generator that owns the event loop lifecycle.
Changes
apps/cloud/apps/ai/views.py: AddedChatExecutionContextdataclass,resolve_chat_scope(),generate_chat_sse(). Slimmedchat_streamfrom ~120 lines to ~30 lines.tests/cloud/test_chat_stream_refactor.py(new): 17 tests verifying the new structure exists andchat_streamcomposes the helpers.
Validation
- 66/66 tests pass across
test_chat_stream.py+test_chat_stream_refactor.py - 215/215 tests pass across all
tests/cloud/+tests/ai/ ruff checkandblack --checkclean
QA Exploration
- [x] QA exploration completed (or N/A for non-UI tasks)
N/A — backend-only refactor, no UI changes.
Review Feedback
- Manager review (
just review) approved with follow-up fixes. - Restored
org_slugin exception logging so production failures keep their original diagnostic context. - Removed
asyncio.set_event_loop(loop)fromgenerate_chat_sse()to avoid mutating thread-local event loop state in request threads. - Manager validation after review fixes:
uv run pytest tests/cloud/test_chat_stream.py tests/cloud/test_chat_stream_refactor.py tests/ai -quv run ruff check apps/cloud/apps/ai/views.py tests/cloud/test_chat_stream_refactor.pyuv run black --check apps/cloud/apps/ai/views.py tests/cloud/test_chat_stream_refactor.py- [ ] Review cleared