Metadata-Version: 2.5
Name: qraken-remote-chatbot
Version: 0.1.1
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.
Project-URL: Homepage, https://github.com/RemoGrillo/QRAKEN_remote_chatbot
Project-URL: Documentation, https://github.com/RemoGrillo/QRAKEN_remote_chatbot#readme
Project-URL: Issues, https://github.com/RemoGrillo/QRAKEN_remote_chatbot/issues
Project-URL: Source, https://github.com/RemoGrillo/QRAKEN_remote_chatbot
Author-email: Remo Grillo <rgrillo@itatti.harvard.edu>
License: MIT
License-File: LICENSE
Keywords: chatbot,cidoc-crm,digital-humanities,flask,knowledge-graph,nl2sparql,qraken,rdf,sparql,widget
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: JavaScript
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Text Processing :: Markup :: HTML
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24
Provides-Extra: dev
Requires-Dist: flask>=2.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Provides-Extra: flask
Requires-Dist: flask>=2.0; extra == 'flask'
Description-Content-Type: text/markdown

# QRAKEN Remote Chatbot

Embed a QRAKEN knowledge-graph chatbot in your website. Your visitors ask questions
in plain language; QRAKEN turns them into SPARQL, runs them against your graph, and
answers in prose.

You install one pip package and add one `<script>` tag. You do **not** install the
QRAKEN stack — that runs on a QRAKEN server you point at.

## How it fits together

```
Visitor's browser          Your Flask server              QRAKEN server
┌────────────────┐         ┌──────────────────┐          ┌─────────────────┐
│ chat widget    │ ──────► │ this package     │ ───────► │ /embed API      │
│ (no keys)      │ ◄────── │ (holds the keys) │ ◄─────── │ NTQ · SPARQL    │
└────────────────┘         └──────────────────┘          │ · the model     │
     same-origin                                          └─────────────────┘
```

The browser only ever talks to your own server, so there is **no CORS to configure**
and **no credential in the page**. Your client key and your LLM provider key stay in
your Flask process.

## Install

```bash
pip install qraken-remote-chatbot
```

New to this? **[GETTING_STARTED.md](GETTING_STARTED.md)** is the step-by-step
version, written for someone adding the chatbot to a site for the first time.

## Configure

You need two things:

1. A **client key** (`qrk_…`) from the QRAKEN operator, issued from their admin
   dashboard. It also decides which graphs you may query.
2. An **LLM provider key** of your own — OpenAI, Anthropic, Gemini, … Your account
   is billed for the questions your visitors ask.

Keep both in the environment, never in code:

```bash
export QRAKEN_TENANT_TOKEN=qrk_...
export QRAKEN_LLM_API_KEY=sk-ant-...
export QRAKEN_LLM_PROVIDER=anthropic
export QRAKEN_TTQL=my-graph.ttql
```

## Add it to your app

```python
import os
from flask import Flask
from qraken_remote_chatbot import QrakenConfig, create_blueprint

app = Flask(__name__)

app.register_blueprint(create_blueprint(QrakenConfig(
    tenant_token = os.environ["QRAKEN_TENANT_TOKEN"],
    llm_api_key  = os.environ["QRAKEN_LLM_API_KEY"],
    llm_provider = "anthropic",
    ttql_name    = "my-graph.ttql",
)), url_prefix="/qraken")
```

Or, reading everything from the environment:

```python
app.register_blueprint(create_blueprint(QrakenConfig.from_env()), url_prefix="/qraken")
```

Then, in any page:

```html
<div id="qraken-chat"></div>
<script src="/qraken/widget.js" data-base="/qraken" defer></script>
```

That is the whole integration. The widget renders a launcher in the corner; the
visitor presses **Start a conversation**, gets a greeting describing what the graph
can answer, and chats from there.

## Configuration reference

| Option | Default | What it does |
|---|---|---|
| `tenant_token` | — | **Required.** Your client key from the QRAKEN operator. |
| `ttql_name` | — | **Required.** The graph to answer about, by TTQL filename. |
| `llm_api_key` | `None` | Your provider key. Omit only if the operator covers your usage. |
| `llm_provider` | `anthropic` | `openai` · `anthropic` · `gemini` · `harvard_bedrock` · `lmstudio` |
| `llm_model` | `None` | Model id; the server picks a default when unset. |
| `qraken_url` | `https://qrakenchatbot.remogrillo.me` | The QRAKEN server. |
| `timeout_s` | `180` | A turn runs translation, a SPARQL query and an answer — keep this generous. |
| `session_ttl_s` | `3600` | How long an idle conversation is remembered. |
| `retrieve_literals` | `False` | Value grounding, when the operator has built a Sonar index. |
| `max_question_chars` | `2000` | Rejects oversized questions before they cost anything. |
| `title` / `subtitle` / `start_label` / `placeholder` | — | Widget wording. |
| `show_sparql` | `False` | Reveal the generated query behind a toggle. |
| `display` | `bubble` | `bubble` — launcher + floating panel; `inline` — the chat fills its container. |
| `accent_color` | `#2a78d6` | Widget accent. |
| `position` | `right` | Which corner the launcher sits in. Ignored when inline. |
| `retrieve_literals` | `False` | Value grounding: inject question-relevant literal values into the prompt. |

Only the appearance options ever reach the browser — `public_settings()` builds the
widget's config from an explicit list, so a credential cannot leak by being forgotten.

## Widget script attributes

| Attribute | Default | Meaning |
|---|---|---|
| `data-base` | `/qraken` | Must match your `url_prefix`. |
| `data-target` | `#qraken-chat` | CSS selector of the mount element. |
| `data-open` | `false` | Open the panel on load. |

The widget renders inside a **shadow root**: your site's CSS cannot affect it, and
its styles cannot affect your site. It has no dependencies and no build step.

## Two layouts

**Bubble** (default) — a launcher in the corner opening a floating panel. What you
want on a page that has its own content.

**Inline** — the chat sits in the page where you put the mount element, with no
launcher and nothing floating. For a page whose whole purpose is the chatbot. Give
the container a height; the widget fills it.

```html
<div id="qraken-chat" style="height: 560px"></div>
<script src="/qraken/widget.js" data-base="/qraken" data-display="inline" defer></script>
```

`data-display` overrides the server setting for one page; `display="inline"` in
`QrakenConfig` changes the default for all of them.

## Who pays for the questions

Set `llm_api_key` and your provider account is billed for your visitors' questions.

The QRAKEN operator decides, per client, whether you are *allowed* to omit it and
fall back to their key — off by default. If it is off and you send no key, every
question fails and your log says exactly which setting to change. Your visitors
only ever see a generic apology.

## Endpoints this blueprint adds

| Route | Purpose |
|---|---|
| `POST /qraken/session` | Start a conversation; returns the icebreaker. |
| `POST /qraken/message` | Ask one question. |
| `POST /qraken/end` | Drop a conversation. |
| `GET /qraken/download/<token>.<csv\|json>` | Proxy a result file. |
| `GET /qraken/widget.js`, `/widget.css`, `/config.json` | Widget assets. |

## Running more than one worker

Conversations are held server-side so the browser cannot forge them. The default
store is an in-process dictionary, which is correct for a single worker. Behind
several workers, implement `SessionStore` over shared storage:

```python
from qraken_remote_chatbot import SessionStore, create_blueprint

class RedisSessionStore(SessionStore):
    ...   # create / get / update / delete

app.register_blueprint(
    create_blueprint(config, store=RedisSessionStore(redis_url)),
    url_prefix="/qraken",
)
```

## What the visitor sees when something fails

Upstream errors never reach the page verbatim — internal service names, paths and
query details stay in your server log. The visitor gets a short, safe sentence, and
an expired conversation offers its way back to a new one.

## Try it

```bash
pip install -e ".[dev]"
python examples/demo_app.py       # http://localhost:5000
pytest                            # runs offline against a stubbed server
```

## Requirements

Python 3.9+, Flask 2.0+, and network access to a QRAKEN server.

## Releasing

For maintainers. `scripts/release.sh` runs every pre-flight check, then publishes:

```bash
./scripts/release.sh --bump patch      # 0.1.0 -> 0.1.1, then upload, tag and push
./scripts/release.sh --bump minor      # new features
./scripts/release.sh --dry-run         # every check, no upload
```

It refuses to publish rather than publish something wrong. A PyPI version cannot
be replaced, so all of this happens *before* the upload: the working tree must be
clean, the tests must pass, the version must not already exist on PyPI, `twine
check` must pass, and the artifacts must contain no `.env` and nothing
credential-shaped. On success it commits the version bump, tags `vX.Y.Z`, pushes,
and confirms the release is live.

The token is read from `~/.pypirc` (mode 600) or `TWINE_PASSWORD`. It is never in
this repository — `.gitignore` guards against a stray copy.

## License

MIT
