Reflex Chatbot
A Python web chatbot built with Reflex and reflex-assistant-ui. It talks to any server that implements the OpenAI API and ships preconfigured for:
| Provider | Type | Default URL |
|---|---|---|
| Ollama | local | http://localhost:11434/v1 |
| LM Studio | local | http://localhost:1234/v1 |
llama.cpp (llama-server) |
local | http://localhost:8080/v1 |
| OpenRouter | cloud | https://openrouter.ai/api/v1 |
| OmniRoute | local gateway | http://localhost:20128/v1 |
| Custom (vLLM, LocalAI, LiteLLM, OpenAI, Groq…) | any | CUSTOM_BASE_URL |
Features:
- Streaming replies with a stop button.
- Provider and model pickers in the UI (models are discovered with
GET /v1/models). - Tool calling: current time, calculator and system info, easy to extend.
- Reasoning from thinking models (DeepSeek-R1, Qwen3, gpt-oss…) in a collapsible block,
whether it arrives as
reasoning_content,reasoningor<think>tags. - Message editing and regeneration with conversation branches, markdown, syntax-highlighted code, 👍/👎.
- Fully configured through
.env; API keys never leave the server. - Published on PyPI (
pip install reflex-chatbot) and Docker Hub (ecrespo/reflex-chatbot, amd64 + arm64); onedocker composefile with profiles for an optional Ollama (CPU or NVIDIA GPU).
Install from PyPI
pip install reflex-chatbot # or: uv tool install reflex-chatbot
reflex-chatbot init my-chat # creates my-chat/{rxconfig.py,.env,assets/}
cd my-chat && $EDITOR .env # provider, models, keys
reflex-chatbot run # production, one port → http://localhost:3000
reflex-chatbot run --dev starts the hot-reload server, --port changes the port.
The first run downloads bun and the frontend packages (Reflex needs unzip).
Run with Docker
curl -O https://raw.githubusercontent.com/ecrespo/chatbot/main/docker-compose.yml
curl -o .env https://raw.githubusercontent.com/ecrespo/chatbot/main/.env.example
docker compose up -d # LLM on your machine or OpenRouter
docker compose --profile ollama up -d # + Ollama in compose
See Docker below.
Quick start from source (local Ollama)
Requirements: Python ≥ 3.13, uv and Ollama.
# 1. A model in Ollama
ollama pull llama3.2
ollama serve # unless it already runs as a service
# 2. Dependencies and configuration
uv sync
cp .env.example .env # LLM_PROVIDER=ollama is the default
# 3. Run
uv run reflex run # development → http://localhost:3000
Production mode on a single port (the same thing the Docker image does):
uv run reflex run --env prod --single-port --backend-port 3000
With make: make install, make dev, make prod, make check (make help lists every target).
How it works
flowchart LR
B[Browser<br/>assistant-ui] <-- WebSocket --> S[Reflex backend<br/>ChatState]
S --> L[reflex_chatbot/llm.py<br/>async OpenAI client]
L -->|/v1/chat/completions| O[Ollama]
L --> M[LM Studio]
L --> C[llama.cpp]
L --> R[OpenRouter]
L --> X[OmniRoute]
L --> Y[Custom]
L <-->|tool calls| T[reflex_chatbot/tools.py]
Every provider speaks the same protocol (OpenAI Chat Completions), so a single
client (openai.AsyncOpenAI) covers all of them. A "provider" is just a base URL,
an API key, a default model and optional extras, all defined in .env.
reflex_chatbot/
├── reflex_chatbot.py # page: header, provider/model toolbar, chat
├── cli.py # `reflex-chatbot` command (PyPI install)
├── state.py # ChatState (AssistantUIState + provider/model selection)
├── llm.py # streaming, tool-calling loop, reasoning normalisation
├── tools.py # tools in OpenAI function-calling format
└── settings.py # .env loading and provider presets
Configuration (.env)
Copy .env.example to .env. Variables already set in the process environment
(export, docker -e, compose environment:) take precedence over the file.
CHATBOT_ENV_FILE=/path/other.env loads a different file.
General variables
| Variable | Default | Description |
|---|---|---|
LLM_PROVIDER |
ollama |
Initial provider: ollama, lmstudio, llamacpp, openrouter, omniroute, custom |
LLM_ENABLED_PROVIDERS |
all | Providers shown in the picker, comma-separated |
LLM_TEMPERATURE |
0.7 |
Initial temperature (adjustable in the UI, 0–1.5) |
LLM_MAX_TOKENS |
empty | Output token limit; empty = server default |
LLM_ENABLE_TOOLS |
true |
Enables function calling (toggle in the UI) |
LLM_MAX_TOOL_ROUNDS |
4 |
Maximum model → tool → model rounds |
LLM_SHOW_REASONING |
true |
Shows the reasoning block (toggle in the UI) |
LLM_REASONING_EFFORT |
empty | When set (low/medium/high/none), reasoning_effort is sent |
LLM_REQUEST_TIMEOUT |
120 |
Seconds per request |
LLM_SYSTEM_PROMPT |
see .env.example |
System prompt |
APP_TITLE, APP_WELCOME_TITLE, APP_WELCOME_SUBTITLE |
— | UI texts |
CHAT_STREAM_INTERVAL |
0.05 |
Minimum seconds between pushes to the browser while streaming |
Per-provider variables
Each provider has a prefix: OLLAMA_, LMSTUDIO_, LLAMACPP_, OPENROUTER_,
OMNIROUTE_, CUSTOM_.
| Suffix | Description |
|---|---|
_BASE_URL |
OpenAI-compatible base URL (ends in /v1) |
_API_KEY |
Key. Local servers ignore it; OpenRouter requires it |
_MODEL |
Default model (always offered in the picker) |
_MODELS |
Fixed, comma-separated model list; when empty /v1/models is queried |
_HEADERS |
JSON with extra HTTP headers, e.g. {"X-Org": "demo"} |
_EXTRA_BODY |
JSON merged into every request body (provider-specific parameters) |
_LABEL |
Name shown in the UI |
OpenRouter only: OPENROUTER_HTTP_REFERER and OPENROUTER_APP_TITLE (attribution).
The detailed guide for each platform (how to start the server, what to put in
.env, tool and reasoning support) is in docs/PROVIDERS.md.
Switching providers at a glance:
# LM Studio
LLM_PROVIDER=lmstudio # and in LM Studio: Developer → Start Server
# llama.cpp
LLM_PROVIDER=llamacpp # llama-server -hf ggml-org/gemma-3-4b-it-GGUF --jinja --port 8080
# OpenRouter
LLM_PROVIDER=openrouter
OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=openai/gpt-4o-mini
# OmniRoute
LLM_PROVIDER=omniroute # npx omniroute → http://localhost:20128
OMNIROUTE_MODEL=auto
Using the UI
- Provider: the first picker. Changing it queries that provider's models.
- Model: models offered by the server (or
*_MODELS). Modelos re-queries them (useful afterollama pullor after loading another model in LM Studio). - temp: temperature for the next replies.
- herramientas (tools): turns function calling on/off. If a model does not support tools, the chatbot detects it, retries without them and remembers that for the model.
- razonamiento (reasoning): shows or hides the thinking block.
- Nuevo chat: clears the conversation.
- The header badge shows the connection state (green = connected); hovering shows the URL or the error. When the provider does not answer, a banner explains why and how to start it.
- On messages: copy, regenerate (creates a branch), edit your own messages (creates a branch) and 👍/👎.
The UI labels are in Spanish; change them in reflex_chatbot/reflex_chatbot.py and the APP_* variables.
Docker
Summary (details in docs/DOCKER.md). A single
docker-compose.yml with profiles for the optional services:
cp .env.example .env
docker compose up -d # chatbot only: LLM on your machine or OpenRouter
docker compose --profile ollama up -d # + Ollama (CPU), pulls OLLAMA_PULL_MODELS
docker compose --profile ollama-gpu up -d # + Ollama on an NVIDIA GPU
docker compose --profile '*' down # stop everything
Same with make: make up, make up-ollama, make up-gpu, make down. Or set
COMPOSE_PROFILES=ollama in .env. up pulls ecrespo/reflex-chatbot; add
--build to build from the checkout.
Inside the container localhost is the container itself, so docker-compose.yml
rewrites the local URLs to host.docker.internal (DOCKER_*_BASE_URL variables).
The Ollama URL is chosen automatically: the compose ollama service when a profile
enables it, the host otherwise. On Linux the host server must listen on 0.0.0.0,
e.g. OLLAMA_HOST=0.0.0.0:11434.
To update a deployment: docker compose pull && docker compose up -d.
Development, releases and updates
make install # uv sync + .env
make check # ruff + pytest (CI also builds the wheel and the image)
make update # upgrade uv.lock and re-run the checks
make bump VERSION=0.2.0 && make release # see docs/RELEASING.md
Pushing a vX.Y.Z tag on main runs .github/workflows/release.yml, which
publishes the package to PyPI (Trusted Publishing) and the multi-arch image to
Docker Hub, then creates the GitHub release. Dependabot keeps uv.lock, the
base images and the GitHub Actions up to date. Setup and the full process are in
docs/RELEASING.md.
Extending
New tool — in reflex_chatbot/tools.py:
@tool(
"get_weather",
"Return the current weather for a city.",
{"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
)
async def get_weather(city: str) -> dict:
return {"city": city, "temp_c": 29}
It is sent to the models automatically, and its call and result are rendered in the chat.
New named provider — add an entry to PROVIDER_PRESETS in
reflex_chatbot/settings.py (key, variable prefix, label, default URL) and add its key
to LLM_ENABLED_PROVIDERS. For a one-off endpoint, CUSTOM_* is enough.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| "no se pudo conectar con http://localhost:11434/v1" (could not connect) | Ollama is not running (ollama serve) or the URL is different |
| Works locally but not in Docker | Use host.docker.internal and make the server listen on 0.0.0.0 (see docs/DOCKER.md) |
| "sin modelos" (no models) | No model pulled/loaded: ollama pull …, load one in LM Studio, or set *_MODEL / *_MODELS |
| HTTP 401 from OpenRouter | OPENROUTER_API_KEY is missing or invalid |
| The model does not use tools | Not every model supports them; start llama.cpp with --jinja. Tools can be switched off in the UI |
| Timeouts with large models | Raise LLM_REQUEST_TIMEOUT |
The UI loads but never connects after mapping another port (8080:3000) |
Use the same port on both sides (PORT=8080) or set REFLEX_API_URL |
License
MIT — see LICENSE.
Metadata
Release files for reflex-chatbot 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| reflex_chatbot-0.1.1.tar.gz | 26.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| reflex_chatbot-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.8 kB
Release files / reflex_chatbot-0.1.1.tar.gz
| Download URL | reflex_chatbot-0.1.1.tar.gz |
|---|---|
| Size | 26.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
500a080f2387f2ad0cfb68f4b8121e07ebd9bba99322c6c7f8770bfbeb81b37e
|
|
BLAKE2b-256 checksum How to use checksums |
0b350c4f02356167ab38b4c69a6d251c83b22a644bc1ac01c284f2fa62bc72f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / reflex_chatbot-0.1.1-py3-none-any.whl
| Download URL | reflex_chatbot-0.1.1-py3-none-any.whl |
|---|---|
| Size | 27.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dafd6c4b0d7668228d3df3cdb296598a76c7a3ef46d13a27b14d448915194085
|
|
BLAKE2b-256 checksum How to use checksums |
960ab4a38e7c4bfb75eaa24d1e20f36c5f1e7900ab36928f8d2ded40827de039
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for reflex-chatbot 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for reflex_chatbot-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.