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.
- qraken_remote_chatbot-0.1.0/.gitignore +11 -0
- qraken_remote_chatbot-0.1.0/GETTING_STARTED.md +198 -0
- qraken_remote_chatbot-0.1.0/LICENSE +21 -0
- qraken_remote_chatbot-0.1.0/PKG-INFO +235 -0
- qraken_remote_chatbot-0.1.0/README.md +195 -0
- qraken_remote_chatbot-0.1.0/dummy/.env.example +35 -0
- qraken_remote_chatbot-0.1.0/dummy/README.md +48 -0
- qraken_remote_chatbot-0.1.0/dummy/app.py +128 -0
- qraken_remote_chatbot-0.1.0/dummy/run.sh +100 -0
- qraken_remote_chatbot-0.1.0/dummy/static/site.css +59 -0
- qraken_remote_chatbot-0.1.0/dummy/templates/base.html +27 -0
- qraken_remote_chatbot-0.1.0/dummy/templates/home.html +31 -0
- qraken_remote_chatbot-0.1.0/dummy/templates/inline.html +25 -0
- qraken_remote_chatbot-0.1.0/examples/demo_app.py +76 -0
- qraken_remote_chatbot-0.1.0/pyproject.toml +57 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/__init__.py +67 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/client.py +163 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/config.py +155 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/errors.py +94 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/flask_ext.py +192 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/py.typed +0 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/sessions.py +89 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/static/qraken-widget.css +293 -0
- qraken_remote_chatbot-0.1.0/src/qraken_remote_chatbot/static/qraken-widget.js +373 -0
- qraken_remote_chatbot-0.1.0/tests/test_package.py +270 -0
|
@@ -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
|