fix(chat-ui): replace Chatbot UI with Chainlit#1734
fix(chat-ui): replace Chatbot UI with Chainlit#1734Pouyanpi merged 10 commits intoNVIDIA-NeMo:developfrom
Conversation
Greptile SummaryThis PR replaces the old static Chatbot UI with a Chainlit-based chat interface, moving UI logic into
|
| Filename | Overview |
|---|---|
| nemoguardrails/server/app.py | New Chainlit UI handler — streaming, fallback to generate_async, and per-session config selection all look correct; minor risk of empty assistant messages in history on fully-blocked outputs. |
| nemoguardrails/server/api.py | Chainlit mount added at module level using env-var-based feature flag; relative redirect URL (bare 'chat') sent as Location header is technically non-compliant but works in browsers. |
| nemoguardrails/cli/init.py | Sets NEMO_GUARDRAILS_DISABLE_CHAT_UI env var before importing api.py so the module-level conditional fires correctly; uvicorn and FastAPI import guard consolidated into a single try/except. |
| pyproject.toml | chainlit added as optional extra under [chat-ui] and dev dependency; chat-ui/* removed from package includes. |
| .github/workflows/test-docker.yml | Health-check endpoint updated to /v1/rails/configs (GET), timeout-minutes guard added, and polling interval doubled — all appropriate for Chainlit startup time. |
| nemoguardrails/utils.py | get_chat_ui_data_path helper removed as part of chat-ui cleanup; no other callers remain. |
Sequence Diagram
sequenceDiagram
participant Browser
participant FastAPI as FastAPI (api.py)
participant Chainlit as Chainlit UI (app.py)
participant Rails as LLMRails
Browser->>FastAPI: GET /
FastAPI-->>Browser: 307 Redirect → /chat
Browser->>Chainlit: GET /chat
Chainlit-->>Browser: Chat UI (HTML)
Browser->>Chainlit: on_chat_start
Chainlit->>FastAPI: _discover_configs()
FastAPI-->>Chainlit: [config_ids]
Chainlit-->>Browser: ChatSettings dropdown
Browser->>Chainlit: on_message(user_text)
Chainlit->>FastAPI: _get_rails([config_id])
FastAPI-->>Chainlit: LLMRails instance
alt Streaming supported
Chainlit->>Rails: stream_async(messages)
Rails-->>Chainlit: token chunks
Chainlit-->>Browser: stream_token (incremental)
else StreamingNotSupportedError
Chainlit->>Rails: generate_async(messages)
Rails-->>Chainlit: full response
Chainlit-->>Browser: message.update()
end
Prompt To Fix All With AI
This is a comment left during a code review.
Path: nemoguardrails/server/api.py
Line: 726
Comment:
**Relative redirect URL**
`url="chat"` is a relative URL without a leading slash. While browsers typically resolve this correctly from `/` → `/chat`, HTTP/1.1 (RFC 7231 §7.1.2) specifies that a `Location` header in a 3xx response SHOULD be an absolute URI. Some HTTP clients (e.g. `httpx` following redirects, API testing tools) may not resolve bare-word relative URLs correctly. Using `/chat` is unambiguous and works regardless of client behaviour.
```suggestion
return RedirectResponse(url="/chat")
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: nemoguardrails/server/app.py
Line: 145-146
Comment:
**Empty assistant message appended to history**
If `stream_async` completes without raising but yields no string chunks (e.g. all output is blocked by a guardrail and the blocked-message token flow produces nothing), `full_response` stays `""` and an empty `{"role": "assistant", "content": ""}` gets appended to the conversation history. Some LLM backends reject empty assistant turns on the next request, which would silently break the conversation session. Consider guarding the append:
```suggestion
if full_response:
messages.append({"role": "assistant", "content": full_response})
cl.user_session.set("messages", messages)
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (12): Last reviewed commit: "fix(chat-ui): avoid double LLM call and ..." | Re-trigger Greptile
📝 WalkthroughWalkthroughAdds a field validator to accept the model parameter in OpenAI chat completion requests as either a string or a dictionary with an id key, resolving compatibility with the web UI. Includes a corresponding test case validating dictionary-format model handling. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/server/test_openai_integration.py (1)
89-103: Add a malformed-model negative test to pin validator behavior.This new test covers the happy path well; adding one 422 case for invalid dict shape would make the compatibility contract much safer against regressions.
✅ Suggested test addition
+def test_chat_completion_model_as_dict_invalid_id(): + test_client = TestClient(api.app) + response = test_client.post( + "/v1/chat/completions", + json={ + "model": {"name": "gpt-4o"}, # missing string "id" + "messages": [{"role": "user", "content": "hi"}], + "guardrails": {"config_id": "with_custom_llm"}, + }, + ) + assert response.status_code == 422🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/server/test_openai_integration.py` around lines 89 - 103, Add a negative test alongside test_chat_completion_model_as_dict that posts to "/v1/chat/completions" with an invalid model dict shape (e.g., missing required keys or wrong types) and assert the server returns a 422 status; locate the test near test_chat_completion_model_as_dict in tests/server/test_openai_integration.py, call the same TestClient(api.app), send a malformed "model" payload, and assert response.status_code == 422 and that the response body contains validation error details to pin expected validator behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@tests/server/test_openai_integration.py`:
- Around line 89-103: Add a negative test alongside
test_chat_completion_model_as_dict that posts to "/v1/chat/completions" with an
invalid model dict shape (e.g., missing required keys or wrong types) and assert
the server returns a 422 status; locate the test near
test_chat_completion_model_as_dict in tests/server/test_openai_integration.py,
call the same TestClient(api.app), send a malformed "model" payload, and assert
response.status_code == 422 and that the response body contains validation error
details to pin expected validator behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4e340869-0bc5-4b40-85ef-31d0a6061e8b
📒 Files selected for processing (2)
nemoguardrails/server/schemas/openai.pytests/server/test_openai_integration.py
c167f3a to
9d024bc
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
thank you @christinaexyou , the issue is deeper. If you run nemoguardrails server you can see the UI is not responsive. It does not crash but does not work. Let's see if we want to adapt to this existing UI or come up with a new one. |
|
ack thanks @Pouyanpi, I'll look into this and we can discuss further during our sync. |
9d024bc to
7981f52
Compare
7981f52 to
1aedd78
Compare
|
@Pouyanpi i reverted my previous changes and updated the title/description of this PR |
1aedd78 to
b3d55f5
Compare
Documentation preview |
HEAD on / returns 405 after the chainlit redirect, hanging the wait loop. Poll /v1/rails/configs and cap the step at 3 minutes.
Catches StreamingNotSupportedError from stream_async() and falls through to generate_async, so configs with output rails but streaming disabled work in the chat UI. Also stops leaking exception text into user-facing messages and removes the over-defensive type ladder around the generate_async response. Addresses greptile P1 (no streaming fallback) and review nits on error exposure / type narrowing. WIP
Wraps 'from chainlit.utils import mount_chainlit' in try/except so the server imports cleanly when chainlit isn't installed (logs a warning, falls through to a status endpoint). Moves the mount call out of lifespan to module level to fix the unresponsive-UI symptom. The --disable-chat-ui CLI flag is now forwarded via NEMO_GUARDRAILS_DISABLE_CHAT_UI so it can be read at module load time, before the mount. Addresses greptile P1 (unconditional chainlit import) and Pouyanpi's maintainer blocker (UI not responsive). WIP fix
The chat-ui/ directory was deleted in this PR; the matching include glob in pyproject.toml is dead config. Addresses greptile suggestion.
b3d55f5 to
69eb310
Compare
|
Thank you @christinaexyou ! Nice job. I just made some minor changes 👍🏻 I'll follow-up with a PR to customize chainlit a bit. |
9be8f93 to
98646f5
Compare
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
NO! Why cannot you just post your comments in the first iteration ? |
Description
This PR replaces Chatbot UI with Chainlit by making the following changes:
chat-uinemoguardrails/server/app.pywhich contains our Chainlit UI logicnemoguardrails/server/api.pyto replace Chatbot UI with ChainlitDemo
I tested my changes by using the following
challenges.jsonfile in tandem withexamples/configs/injection_detectionand running the server:Running the server - by default the Chainlit UI is exposed on port 8000:
Screen.Recording.2026-04-21.at.12.54.58.PM.mov
Related Issue(s)
Fixes #1728
Checklist