qraken-remote-chatbot 0.1.0__tar.gz

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.
Files changed (25) hide show
  1. qraken_remote_chatbot-0.1.0/.gitignore +11 -0
  2. qraken_remote_chatbot-0.1.0/GETTING_STARTED.md +198 -0
  3. qraken_remote_chatbot-0.1.0/LICENSE +21 -0
  4. qraken_remote_chatbot-0.1.0/PKG-INFO +235 -0
  5. qraken_remote_chatbot-0.1.0/README.md +195 -0
  6. qraken_remote_chatbot-0.1.0/dummy/.env.example +35 -0
  7. qraken_remote_chatbot-0.1.0/dummy/README.md +48 -0
  8. qraken_remote_chatbot-0.1.0/dummy/app.py +128 -0
  9. qraken_remote_chatbot-0.1.0/dummy/run.sh +100 -0
  10. qraken_remote_chatbot-0.1.0/dummy/static/site.css +59 -0
  11. qraken_remote_chatbot-0.1.0/dummy/templates/base.html +27 -0
  12. qraken_remote_chatbot-0.1.0/dummy/templates/home.html +31 -0
  13. qraken_remote_chatbot-0.1.0/dummy/templates/inline.html +25 -0
  14. qraken_remote_chatbot-0.1.0/examples/demo_app.py +76 -0
  15. qraken_remote_chatbot-0.1.0/pyproject.toml +57 -0
  16. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/__init__.py +67 -0
  17. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/client.py +163 -0
  18. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/config.py +155 -0
  19. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/errors.py +94 -0
  20. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/flask_ext.py +192 -0
  21. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/py.typed +0 -0
  22. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/sessions.py +89 -0
  23. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/static/qraken-widget.css +293 -0
  24. qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/static/qraken-widget.js +373 -0
  25. qraken_remote_chatbot-0.1.0/tests/test_package.py +270 -0
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+
10
+ # Local test config for the dummy site: holds a real client key.
11
+ dummy/.env
@@ -0,0 +1,198 @@
1
+ # Adding the chatbot to your site
2
+
3
+ A visitor asks a question in plain language; the chatbot turns it into a database
4
+ query, runs it against the knowledge graph, and answers in prose.
5
+
6
+ You install one Python package and add one `<script>` tag. You do **not** install
7
+ or run any of the knowledge-graph machinery — that stays on the QRAKEN server.
8
+
9
+ ---
10
+
11
+ ## What you need
12
+
13
+ **From whoever runs the QRAKEN server, two things:**
14
+
15
+ | | Looks like | What it is |
16
+ |---|---|---|
17
+ | **Client key** | `qrk_x7Kd…` | Identifies your site and decides which graphs you may query |
18
+ | **Graph name** | `something.ttql` | The knowledge graph your chatbot answers about |
19
+
20
+ **From your own AI provider account, one thing:**
21
+
22
+ | | Looks like | What it is |
23
+ |---|---|---|
24
+ | **Provider API key** | `sk-ant-…` | Your Anthropic / OpenAI / Google key. Your account pays for the questions your visitors ask. |
25
+
26
+ Anthropic keys come from [console.anthropic.com](https://console.anthropic.com/settings/keys),
27
+ OpenAI's from [platform.openai.com](https://platform.openai.com/api-keys). Put a
28
+ small spending limit on the key while you experiment.
29
+
30
+ ---
31
+
32
+ ## 1. Install
33
+
34
+ ```bash
35
+ pip install qraken-remote-chatbot
36
+ ```
37
+
38
+ ## 2. Keep the keys out of your code
39
+
40
+ Both keys are secrets. Put them in your environment — the same place you keep
41
+ your database password — never in a source file and never in a template.
42
+
43
+ ```bash
44
+ export QRAKEN_TENANT_TOKEN="qrk_..." # from the QRAKEN operator
45
+ export QRAKEN_TTQL="something.ttql" # the graph name they gave you
46
+ export QRAKEN_LLM_API_KEY="sk-ant-..." # yours
47
+ export QRAKEN_LLM_PROVIDER="anthropic" # or openai, gemini
48
+ ```
49
+
50
+ If you keep a `.env` file, make sure it is in `.gitignore` before you write a key
51
+ into it.
52
+
53
+ ## 3. Register it in your Flask app
54
+
55
+ ```python
56
+ from qraken_remote_chatbot import QrakenConfig, create_blueprint
57
+
58
+ app.register_blueprint(
59
+ create_blueprint(QrakenConfig.from_env()),
60
+ url_prefix="/qraken",
61
+ )
62
+ ```
63
+
64
+ `QrakenConfig.from_env()` reads the four variables above. To set anything in code
65
+ instead, pass it directly:
66
+
67
+ ```python
68
+ QrakenConfig.from_env(
69
+ title="Ask the collection",
70
+ subtitle="Questions about the archive, in plain language.",
71
+ accent_color="#8a4b2a",
72
+ )
73
+ ```
74
+
75
+ ## 4. Add it to a page
76
+
77
+ ```html
78
+ <div id="qraken-chat"></div>
79
+ <script src="/qraken/widget.js" data-base="/qraken" defer></script>
80
+ ```
81
+
82
+ That is the whole front-end integration. A chat launcher appears in the corner;
83
+ your visitor presses **Start a conversation**, gets a greeting describing what the
84
+ graph can answer, and asks away.
85
+
86
+ The widget renders inside a *shadow root*, so your site's CSS cannot affect it and
87
+ its styles cannot affect your site. It loads no external code and needs no build
88
+ step.
89
+
90
+ ---
91
+
92
+ ## Check it works
93
+
94
+ Open the page and press the launcher. If the greeting describes the actual
95
+ contents of the graph, everything is connected.
96
+
97
+ If not, the answer is almost always in **your server's log**, which is written to
98
+ be read:
99
+
100
+ | In your log | What to do |
101
+ |---|---|
102
+ | `the anthropic provider rejected the configured llm_api_key` | wrong key, or `QRAKEN_LLM_PROVIDER` names a provider the key does not belong to |
103
+ | `QRAKEN requires this client to send its own LLM provider key` | `QRAKEN_LLM_API_KEY` is not set |
104
+ | `QRAKEN rejected this client key` | the client key is wrong, or has been rotated or disabled |
105
+ | `QRAKEN returned its fallback greeting` | no model answered — visitors are seeing a generic message |
106
+ | `TTQL '…' is not allowed for this client` | `QRAKEN_TTQL` names a graph your key may not query — ask the operator |
107
+
108
+ Your visitors never see any of that. They get a short, plain apology, because the
109
+ details would tell them about servers and services that are none of their
110
+ business.
111
+
112
+ ---
113
+
114
+ ## Two layouts
115
+
116
+ **Bubble** (the default) — a launcher in the corner opening a floating panel. What
117
+ you want on a page that has its own content.
118
+
119
+ **Inline** — the chat sits in the page, no launcher, nothing floating. For a page
120
+ whose whole purpose is asking questions. Give the container a height:
121
+
122
+ ```html
123
+ <div id="qraken-chat" style="height: 560px"></div>
124
+ <script src="/qraken/widget.js" data-base="/qraken" data-display="inline" defer></script>
125
+ ```
126
+
127
+ ## Settings worth knowing
128
+
129
+ Everything below is optional, passed to `QrakenConfig`:
130
+
131
+ | Setting | Default | What it does |
132
+ |---|---|---|
133
+ | `title`, `subtitle` | | The panel header |
134
+ | `start_label`, `placeholder` | | Button and input wording |
135
+ | `accent_color` | `#2a78d6` | Matches the widget to your site |
136
+ | `position` | `right` | Which corner the launcher sits in |
137
+ | `display` | `bubble` | `bubble` or `inline` |
138
+ | `show_sparql` | `False` | Adds a "show the query" toggle under each answer. Useful for expert audiences, noise for everyone else. |
139
+ | `llm_model` | | A specific model. Leave unset and the server picks a sensible default. |
140
+ | `retrieve_literals` | `False` | Helps when questions name specific titles, people or places |
141
+ | `session_ttl_s` | `3600` | How long an idle conversation is remembered |
142
+ | `max_question_chars` | `2000` | Rejects oversized questions before they cost anything |
143
+
144
+ ## Answers take time
145
+
146
+ A question runs three steps on the server: translate to a query, run it against
147
+ the graph, put the result into words. Ten to forty seconds is normal, and the
148
+ widget shows a thinking indicator throughout.
149
+
150
+ If your site sits behind a reverse proxy (nginx, Apache, a CDN), make sure its
151
+ read timeout is **at least 180 seconds** for the `/qraken/` path. A proxy that
152
+ gives up at 60s will cut off answers the server is still producing — which looks
153
+ like a broken chatbot but is only impatience.
154
+
155
+ ## In production
156
+
157
+ Conversations are remembered on your server, not in the browser, so a visitor
158
+ cannot tamper with the context sent to the graph. The default store keeps them in
159
+ memory, which is right for **one** worker process.
160
+
161
+ If you run several workers (gunicorn with `-w 4`, several containers), each has
162
+ its own memory and a visitor's follow-up question may land on a worker that has
163
+ never heard of them. Either pin sessions to a worker at the load balancer, or
164
+ provide shared storage:
165
+
166
+ ```python
167
+ from qraken_remote_chatbot import SessionStore, create_blueprint
168
+
169
+ class RedisSessionStore(SessionStore):
170
+ def create(self, data): ... # return a new session id
171
+ def get(self, session_id): ... # return the stored dict, or None
172
+ def update(self, session_id, data): ...
173
+ def delete(self, session_id): ...
174
+
175
+ app.register_blueprint(
176
+ create_blueprint(config, store=RedisSessionStore(url)),
177
+ url_prefix="/qraken",
178
+ )
179
+ ```
180
+
181
+ ## What not to do
182
+
183
+ - **Never put either key in a template, a JavaScript file, or anything the browser
184
+ downloads.** Both belong on the server. The widget is built so it never needs
185
+ them: it talks only to your own domain.
186
+ - **Do not commit a `.env` file.** Add it to `.gitignore` first.
187
+ - **Do not proxy `/qraken/` to the QRAKEN server directly.** The package exists to
188
+ sit in between; bypassing it would mean putting your keys in the browser.
189
+
190
+ ## Getting help
191
+
192
+ Something wrong in the chatbot's *answers* — wrong numbers, missing records,
193
+ misread questions — is about the graph or the model, so it goes to whoever runs
194
+ the QRAKEN server.
195
+
196
+ Something wrong in the *installation* — the widget not appearing, an error in your
197
+ log, a question that never returns — is in this package:
198
+ https://github.com/RemoGrillo/QRAKEN_remote_chatbot/issues
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Remo Grillo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: qraken-remote-chatbot
3
+ Version: 0.1.0
4
+ Summary: Embed a QRAKEN knowledge-graph chatbot in any site: a chat widget plus the server-side proxy that keeps your API keys out of the browser.
5
+ Project-URL: Homepage, https://github.com/RemoGrillo/QRAKEN_remote_chatbot
6
+ Project-URL: Documentation, https://github.com/RemoGrillo/QRAKEN_remote_chatbot#readme
7
+ Project-URL: Issues, https://github.com/RemoGrillo/QRAKEN_remote_chatbot/issues
8
+ Project-URL: Source, https://github.com/RemoGrillo/QRAKEN_remote_chatbot
9
+ Author-email: Remo Grillo <rgrillo@itatti.harvard.edu>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: chatbot,cidoc-crm,digital-humanities,flask,knowledge-graph,nl2sparql,qraken,rdf,sparql,widget
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: Flask
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Science/Research
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: JavaScript
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
28
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
29
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
30
+ Classifier: Topic :: Text Processing :: Markup :: HTML
31
+ Classifier: Typing :: Typed
32
+ Requires-Python: >=3.9
33
+ Requires-Dist: httpx>=0.24
34
+ Provides-Extra: dev
35
+ Requires-Dist: flask>=2.0; extra == 'dev'
36
+ Requires-Dist: pytest>=7.0; extra == 'dev'
37
+ Provides-Extra: flask
38
+ Requires-Dist: flask>=2.0; extra == 'flask'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # QRAKEN Remote Chatbot
42
+
43
+ Embed a QRAKEN knowledge-graph chatbot in your website. Your visitors ask questions
44
+ in plain language; QRAKEN turns them into SPARQL, runs them against your graph, and
45
+ answers in prose.
46
+
47
+ You install one pip package and add one `<script>` tag. You do **not** install the
48
+ QRAKEN stack — that runs on a QRAKEN server you point at.
49
+
50
+ ## How it fits together
51
+
52
+ ```
53
+ Visitor's browser Your Flask server QRAKEN server
54
+ ┌────────────────┐ ┌──────────────────┐ ┌─────────────────┐
55
+ │ chat widget │ ──────► │ this package │ ───────► │ /embed API │
56
+ │ (no keys) │ ◄────── │ (holds the keys) │ ◄─────── │ NTQ · SPARQL │
57
+ └────────────────┘ └──────────────────┘ │ · the model │
58
+ same-origin └─────────────────┘
59
+ ```
60
+
61
+ The browser only ever talks to your own server, so there is **no CORS to configure**
62
+ and **no credential in the page**. Your client key and your LLM provider key stay in
63
+ your Flask process.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install qraken-remote-chatbot
69
+ ```
70
+
71
+ New to this? **[GETTING_STARTED.md](GETTING_STARTED.md)** is the step-by-step
72
+ version, written for someone adding the chatbot to a site for the first time.
73
+
74
+ ## Configure
75
+
76
+ You need two things:
77
+
78
+ 1. A **client key** (`qrk_…`) from the QRAKEN operator, issued from their admin
79
+ dashboard. It also decides which graphs you may query.
80
+ 2. An **LLM provider key** of your own — OpenAI, Anthropic, Gemini, … Your account
81
+ is billed for the questions your visitors ask.
82
+
83
+ Keep both in the environment, never in code:
84
+
85
+ ```bash
86
+ export QRAKEN_TENANT_TOKEN=qrk_...
87
+ export QRAKEN_LLM_API_KEY=sk-ant-...
88
+ export QRAKEN_LLM_PROVIDER=anthropic
89
+ export QRAKEN_TTQL=my-graph.ttql
90
+ ```
91
+
92
+ ## Add it to your app
93
+
94
+ ```python
95
+ import os
96
+ from flask import Flask
97
+ from qraken_remote_chatbot import QrakenConfig, create_blueprint
98
+
99
+ app = Flask(__name__)
100
+
101
+ app.register_blueprint(create_blueprint(QrakenConfig(
102
+ tenant_token = os.environ["QRAKEN_TENANT_TOKEN"],
103
+ llm_api_key = os.environ["QRAKEN_LLM_API_KEY"],
104
+ llm_provider = "anthropic",
105
+ ttql_name = "my-graph.ttql",
106
+ )), url_prefix="/qraken")
107
+ ```
108
+
109
+ Or, reading everything from the environment:
110
+
111
+ ```python
112
+ app.register_blueprint(create_blueprint(QrakenConfig.from_env()), url_prefix="/qraken")
113
+ ```
114
+
115
+ Then, in any page:
116
+
117
+ ```html
118
+ <div id="qraken-chat"></div>
119
+ <script src="/qraken/widget.js" data-base="/qraken" defer></script>
120
+ ```
121
+
122
+ That is the whole integration. The widget renders a launcher in the corner; the
123
+ visitor presses **Start a conversation**, gets a greeting describing what the graph
124
+ can answer, and chats from there.
125
+
126
+ ## Configuration reference
127
+
128
+ | Option | Default | What it does |
129
+ |---|---|---|
130
+ | `tenant_token` | — | **Required.** Your client key from the QRAKEN operator. |
131
+ | `ttql_name` | — | **Required.** The graph to answer about, by TTQL filename. |
132
+ | `llm_api_key` | `None` | Your provider key. Omit only if the operator covers your usage. |
133
+ | `llm_provider` | `anthropic` | `openai` · `anthropic` · `gemini` · `harvard_bedrock` · `lmstudio` |
134
+ | `llm_model` | `None` | Model id; the server picks a default when unset. |
135
+ | `qraken_url` | `https://qrakenchatbot.remogrillo.me` | The QRAKEN server. |
136
+ | `timeout_s` | `180` | A turn runs translation, a SPARQL query and an answer — keep this generous. |
137
+ | `session_ttl_s` | `3600` | How long an idle conversation is remembered. |
138
+ | `retrieve_literals` | `False` | Value grounding, when the operator has built a Sonar index. |
139
+ | `max_question_chars` | `2000` | Rejects oversized questions before they cost anything. |
140
+ | `title` / `subtitle` / `start_label` / `placeholder` | — | Widget wording. |
141
+ | `show_sparql` | `False` | Reveal the generated query behind a toggle. |
142
+ | `display` | `bubble` | `bubble` — launcher + floating panel; `inline` — the chat fills its container. |
143
+ | `accent_color` | `#2a78d6` | Widget accent. |
144
+ | `position` | `right` | Which corner the launcher sits in. Ignored when inline. |
145
+ | `retrieve_literals` | `False` | Value grounding: inject question-relevant literal values into the prompt. |
146
+
147
+ Only the appearance options ever reach the browser — `public_settings()` builds the
148
+ widget's config from an explicit list, so a credential cannot leak by being forgotten.
149
+
150
+ ## Widget script attributes
151
+
152
+ | Attribute | Default | Meaning |
153
+ |---|---|---|
154
+ | `data-base` | `/qraken` | Must match your `url_prefix`. |
155
+ | `data-target` | `#qraken-chat` | CSS selector of the mount element. |
156
+ | `data-open` | `false` | Open the panel on load. |
157
+
158
+ The widget renders inside a **shadow root**: your site's CSS cannot affect it, and
159
+ its styles cannot affect your site. It has no dependencies and no build step.
160
+
161
+ ## Two layouts
162
+
163
+ **Bubble** (default) — a launcher in the corner opening a floating panel. What you
164
+ want on a page that has its own content.
165
+
166
+ **Inline** — the chat sits in the page where you put the mount element, with no
167
+ launcher and nothing floating. For a page whose whole purpose is the chatbot. Give
168
+ the container a height; the widget fills it.
169
+
170
+ ```html
171
+ <div id="qraken-chat" style="height: 560px"></div>
172
+ <script src="/qraken/widget.js" data-base="/qraken" data-display="inline" defer></script>
173
+ ```
174
+
175
+ `data-display` overrides the server setting for one page; `display="inline"` in
176
+ `QrakenConfig` changes the default for all of them.
177
+
178
+ ## Who pays for the questions
179
+
180
+ Set `llm_api_key` and your provider account is billed for your visitors' questions.
181
+
182
+ The QRAKEN operator decides, per client, whether you are *allowed* to omit it and
183
+ fall back to their key — off by default. If it is off and you send no key, every
184
+ question fails and your log says exactly which setting to change. Your visitors
185
+ only ever see a generic apology.
186
+
187
+ ## Endpoints this blueprint adds
188
+
189
+ | Route | Purpose |
190
+ |---|---|
191
+ | `POST /qraken/session` | Start a conversation; returns the icebreaker. |
192
+ | `POST /qraken/message` | Ask one question. |
193
+ | `POST /qraken/end` | Drop a conversation. |
194
+ | `GET /qraken/download/<token>.<csv\|json>` | Proxy a result file. |
195
+ | `GET /qraken/widget.js`, `/widget.css`, `/config.json` | Widget assets. |
196
+
197
+ ## Running more than one worker
198
+
199
+ Conversations are held server-side so the browser cannot forge them. The default
200
+ store is an in-process dictionary, which is correct for a single worker. Behind
201
+ several workers, implement `SessionStore` over shared storage:
202
+
203
+ ```python
204
+ from qraken_remote_chatbot import SessionStore, create_blueprint
205
+
206
+ class RedisSessionStore(SessionStore):
207
+ ... # create / get / update / delete
208
+
209
+ app.register_blueprint(
210
+ create_blueprint(config, store=RedisSessionStore(redis_url)),
211
+ url_prefix="/qraken",
212
+ )
213
+ ```
214
+
215
+ ## What the visitor sees when something fails
216
+
217
+ Upstream errors never reach the page verbatim — internal service names, paths and
218
+ query details stay in your server log. The visitor gets a short, safe sentence, and
219
+ an expired conversation offers its way back to a new one.
220
+
221
+ ## Try it
222
+
223
+ ```bash
224
+ pip install -e ".[dev]"
225
+ python examples/demo_app.py # http://localhost:5000
226
+ pytest # runs offline against a stubbed server
227
+ ```
228
+
229
+ ## Requirements
230
+
231
+ Python 3.9+, Flask 2.0+, and network access to a QRAKEN server.
232
+
233
+ ## License
234
+
235
+ MIT
@@ -0,0 +1,195 @@
1
+ # QRAKEN Remote Chatbot
2
+
3
+ Embed a QRAKEN knowledge-graph chatbot in your website. Your visitors ask questions
4
+ in plain language; QRAKEN turns them into SPARQL, runs them against your graph, and
5
+ answers in prose.
6
+
7
+ You install one pip package and add one `<script>` tag. You do **not** install the
8
+ QRAKEN stack — that runs on a QRAKEN server you point at.
9
+
10
+ ## How it fits together
11
+
12
+ ```
13
+ Visitor's browser Your Flask server QRAKEN server
14
+ ┌────────────────┐ ┌──────────────────┐ ┌─────────────────┐
15
+ │ chat widget │ ──────► │ this package │ ───────► │ /embed API │
16
+ │ (no keys) │ ◄────── │ (holds the keys) │ ◄─────── │ NTQ · SPARQL │
17
+ └────────────────┘ └──────────────────┘ │ · the model │
18
+ same-origin └─────────────────┘
19
+ ```
20
+
21
+ The browser only ever talks to your own server, so there is **no CORS to configure**
22
+ and **no credential in the page**. Your client key and your LLM provider key stay in
23
+ your Flask process.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install qraken-remote-chatbot
29
+ ```
30
+
31
+ New to this? **[GETTING_STARTED.md](GETTING_STARTED.md)** is the step-by-step
32
+ version, written for someone adding the chatbot to a site for the first time.
33
+
34
+ ## Configure
35
+
36
+ You need two things:
37
+
38
+ 1. A **client key** (`qrk_…`) from the QRAKEN operator, issued from their admin
39
+ dashboard. It also decides which graphs you may query.
40
+ 2. An **LLM provider key** of your own — OpenAI, Anthropic, Gemini, … Your account
41
+ is billed for the questions your visitors ask.
42
+
43
+ Keep both in the environment, never in code:
44
+
45
+ ```bash
46
+ export QRAKEN_TENANT_TOKEN=qrk_...
47
+ export QRAKEN_LLM_API_KEY=sk-ant-...
48
+ export QRAKEN_LLM_PROVIDER=anthropic
49
+ export QRAKEN_TTQL=my-graph.ttql
50
+ ```
51
+
52
+ ## Add it to your app
53
+
54
+ ```python
55
+ import os
56
+ from flask import Flask
57
+ from qraken_remote_chatbot import QrakenConfig, create_blueprint
58
+
59
+ app = Flask(__name__)
60
+
61
+ app.register_blueprint(create_blueprint(QrakenConfig(
62
+ tenant_token = os.environ["QRAKEN_TENANT_TOKEN"],
63
+ llm_api_key = os.environ["QRAKEN_LLM_API_KEY"],
64
+ llm_provider = "anthropic",
65
+ ttql_name = "my-graph.ttql",
66
+ )), url_prefix="/qraken")
67
+ ```
68
+
69
+ Or, reading everything from the environment:
70
+
71
+ ```python
72
+ app.register_blueprint(create_blueprint(QrakenConfig.from_env()), url_prefix="/qraken")
73
+ ```
74
+
75
+ Then, in any page:
76
+
77
+ ```html
78
+ <div id="qraken-chat"></div>
79
+ <script src="/qraken/widget.js" data-base="/qraken" defer></script>
80
+ ```
81
+
82
+ That is the whole integration. The widget renders a launcher in the corner; the
83
+ visitor presses **Start a conversation**, gets a greeting describing what the graph
84
+ can answer, and chats from there.
85
+
86
+ ## Configuration reference
87
+
88
+ | Option | Default | What it does |
89
+ |---|---|---|
90
+ | `tenant_token` | — | **Required.** Your client key from the QRAKEN operator. |
91
+ | `ttql_name` | — | **Required.** The graph to answer about, by TTQL filename. |
92
+ | `llm_api_key` | `None` | Your provider key. Omit only if the operator covers your usage. |
93
+ | `llm_provider` | `anthropic` | `openai` · `anthropic` · `gemini` · `harvard_bedrock` · `lmstudio` |
94
+ | `llm_model` | `None` | Model id; the server picks a default when unset. |
95
+ | `qraken_url` | `https://qrakenchatbot.remogrillo.me` | The QRAKEN server. |
96
+ | `timeout_s` | `180` | A turn runs translation, a SPARQL query and an answer — keep this generous. |
97
+ | `session_ttl_s` | `3600` | How long an idle conversation is remembered. |
98
+ | `retrieve_literals` | `False` | Value grounding, when the operator has built a Sonar index. |
99
+ | `max_question_chars` | `2000` | Rejects oversized questions before they cost anything. |
100
+ | `title` / `subtitle` / `start_label` / `placeholder` | — | Widget wording. |
101
+ | `show_sparql` | `False` | Reveal the generated query behind a toggle. |
102
+ | `display` | `bubble` | `bubble` — launcher + floating panel; `inline` — the chat fills its container. |
103
+ | `accent_color` | `#2a78d6` | Widget accent. |
104
+ | `position` | `right` | Which corner the launcher sits in. Ignored when inline. |
105
+ | `retrieve_literals` | `False` | Value grounding: inject question-relevant literal values into the prompt. |
106
+
107
+ Only the appearance options ever reach the browser — `public_settings()` builds the
108
+ widget's config from an explicit list, so a credential cannot leak by being forgotten.
109
+
110
+ ## Widget script attributes
111
+
112
+ | Attribute | Default | Meaning |
113
+ |---|---|---|
114
+ | `data-base` | `/qraken` | Must match your `url_prefix`. |
115
+ | `data-target` | `#qraken-chat` | CSS selector of the mount element. |
116
+ | `data-open` | `false` | Open the panel on load. |
117
+
118
+ The widget renders inside a **shadow root**: your site's CSS cannot affect it, and
119
+ its styles cannot affect your site. It has no dependencies and no build step.
120
+
121
+ ## Two layouts
122
+
123
+ **Bubble** (default) — a launcher in the corner opening a floating panel. What you
124
+ want on a page that has its own content.
125
+
126
+ **Inline** — the chat sits in the page where you put the mount element, with no
127
+ launcher and nothing floating. For a page whose whole purpose is the chatbot. Give
128
+ the container a height; the widget fills it.
129
+
130
+ ```html
131
+ <div id="qraken-chat" style="height: 560px"></div>
132
+ <script src="/qraken/widget.js" data-base="/qraken" data-display="inline" defer></script>
133
+ ```
134
+
135
+ `data-display` overrides the server setting for one page; `display="inline"` in
136
+ `QrakenConfig` changes the default for all of them.
137
+
138
+ ## Who pays for the questions
139
+
140
+ Set `llm_api_key` and your provider account is billed for your visitors' questions.
141
+
142
+ The QRAKEN operator decides, per client, whether you are *allowed* to omit it and
143
+ fall back to their key — off by default. If it is off and you send no key, every
144
+ question fails and your log says exactly which setting to change. Your visitors
145
+ only ever see a generic apology.
146
+
147
+ ## Endpoints this blueprint adds
148
+
149
+ | Route | Purpose |
150
+ |---|---|
151
+ | `POST /qraken/session` | Start a conversation; returns the icebreaker. |
152
+ | `POST /qraken/message` | Ask one question. |
153
+ | `POST /qraken/end` | Drop a conversation. |
154
+ | `GET /qraken/download/<token>.<csv\|json>` | Proxy a result file. |
155
+ | `GET /qraken/widget.js`, `/widget.css`, `/config.json` | Widget assets. |
156
+
157
+ ## Running more than one worker
158
+
159
+ Conversations are held server-side so the browser cannot forge them. The default
160
+ store is an in-process dictionary, which is correct for a single worker. Behind
161
+ several workers, implement `SessionStore` over shared storage:
162
+
163
+ ```python
164
+ from qraken_remote_chatbot import SessionStore, create_blueprint
165
+
166
+ class RedisSessionStore(SessionStore):
167
+ ... # create / get / update / delete
168
+
169
+ app.register_blueprint(
170
+ create_blueprint(config, store=RedisSessionStore(redis_url)),
171
+ url_prefix="/qraken",
172
+ )
173
+ ```
174
+
175
+ ## What the visitor sees when something fails
176
+
177
+ Upstream errors never reach the page verbatim — internal service names, paths and
178
+ query details stay in your server log. The visitor gets a short, safe sentence, and
179
+ an expired conversation offers its way back to a new one.
180
+
181
+ ## Try it
182
+
183
+ ```bash
184
+ pip install -e ".[dev]"
185
+ python examples/demo_app.py # http://localhost:5000
186
+ pytest # runs offline against a stubbed server
187
+ ```
188
+
189
+ ## Requirements
190
+
191
+ Python 3.9+, Flask 2.0+, and network access to a QRAKEN server.
192
+
193
+ ## License
194
+
195
+ MIT