Documentation
Integrating a NativeHelpy assistant
A NativeHelpy assistant answers your users from your own content (a website, a help centre,
documentation), in their language, with a link to where each answer came from. This page explains how
we build one for you, what to prepare, and how to put it into your website or your app.
Overview
The assistant is your content plus NativeSoft's pre-trained model. Adding your content does not
retrain the model: we read your pages, split them into sections that keep their links, and index
them. A question is matched against that index and answered from the closest sections.
You receive one assistant, built and tested on your content, and you choose where it runs:
| Where | What you receive | Typical use |
| Hosted by NativeSoft | An address for your assistant, plus a
chat you add to your pages with one script tag (or the HTTP API for your own interface).
Nothing to install | Websites and help centres that want nothing to run |
| On your server | The assistant server, your sealed assistant
file, its library and your licence file, plus the same chat and API | Websites, help
centres, web apps, internal tools that must run on your own machines |
| On the device | The engine as a Dart package and your
assistant as a sealed model file, ready to ship inside the app | Mobile and desktop
apps; works offline, needs no permissions |
All three run on an ordinary CPU, with no GPU and no outside AI service. Questions and answers stay
on the machine that runs the assistant. On your machines, the assistant is delivered
sealed: it runs, but it cannot be copied out and used elsewhere.
How a project runs
- You send your content: the address of your help pages, or the documents
themselves (see Preparing your content), and where the assistant should
appear.
- We build the assistant: we read and index the content, set how it answers and
what it may talk about, and run your test questions against it. You get
the results and can try it before delivery.
- We deliver: we host it for you, or we send the server or the app package with
your sealed assistant file and the licence.
- You integrate it as described below, in your own interface and look.
- When your content changes, the index is rebuilt from it. No retraining (see
Keeping it current).
Preparing your content
The assistant knows only what is in the content you give it, and it answers with your own
sentences. The better your content is organised, the better the answers.
What we can read
- A part of a website: a start address and a path prefix (for example
https://example.com/help/). We follow the links under that prefix and its
sitemap.xml, respect robots.txt, and do not leave the address you
give.
- A sitemap: a
sitemap.xml listing exactly the pages to use.
- A folder of documents: HTML, Markdown or plain-text files. Give each file the
public address where users can read it, so answers can link to it.
Pages behind a login, pages built only by scripts with no text in the HTML, and scanned images of
text cannot be read. Send those as documents instead.
Content that answers well
- One topic per section, under a clear heading. Each heading
(
<h2>, <h3>, or ## in Markdown) starts a
section, and the answer links to it. "How to reset your password" works better than
"Account, part 2".
- Say the answer early. Put the answer in the first sentences of the section,
then the details.
- Use your users' words. If people say "refund", write "refund" next to your
internal term.
- Keep examples as code. Commands, queries and configuration in
<pre> blocks or Markdown code fences can be shown in an answer, ready to
copy.
- Leave out what goes stale. Version numbers and release notes can be left out
of the answers on request, so the assistant never quotes an old version.
- Mark what it should not use. Tell us which pages or sections to leave out:
legal boilerplate, internal pages, duplicates.
Languages
The model reads and answers in 23 languages: English, Romanian, German, Spanish, French, Italian,
Portuguese, Dutch, Polish, Czech, Hungarian, Danish, Swedish, Greek, Turkish, Russian, Ukrainian,
Arabic, Hebrew, Persian, Hindi, Japanese and Korean. Each page should declare its language
(<html lang="...">). For documents, tell us the language. If your content exists
in several languages, send all of them: the assistant answers in the language of the question,
from the content in that language.
How it talks
Send us, for each language you use:
- the assistant's name and your product or company name;
- a greeting and the sentence it says when a question is outside your
content;
- which topics beyond your content it may talk about (jokes, recipes, weather,
sport, general knowledge ...), or none. Greetings, thanks and who made it are always answered.
You also choose how it answers: only with sentences from your content (it never
invents anything, and it is the fastest), or in its own words, checked against your content.
Actions in your app
The assistant can also understand what a user wants to do in your app, and say so to your code.
The live bank demo works this way: "I need last month's statement" opens
Statements for that month, and "Send Ana $250" fills in a transfer. Your app always does the work,
with its own checks, and asks the user to confirm anything that changes data.
For each action, send:
| Field | What it is | Example |
key | The name your code receives, the same in every
language | statement |
title | A short title | Account statements |
text | What the assistant says, at most three sentences | I can
prepare the statement of any month ... |
examples | Several ways users ask for it, in each language | I
need last month's statement; download a bank statement |
{
"statement": {
"title": "Account statements",
"text": "I can prepare the statement of any month in your history. I open Statements and select the period.",
"examples": ["I need last month's statement", "download a bank statement", "monthly report"]
}
}
Details inside a request (a name, an amount, a month) are read by your app from the question, with
your own data. The reply tells you which action was meant; see The reply.
Test questions
Send real questions your users ask, with the answer you expect: the page, section or action that
should answer it. We run them before every delivery and after every update, so you can see that
the assistant answers what it should. One question per line:
{"q": "How do I reset my password?", "lang": "en", "expect": "https://example.com/help/account#reset-password"}
{"q": "I need last month's statement", "lang": "en", "expect": "statement"}
{"q": "What's the weather tomorrow?", "lang": "en", "expect": "refusal"}
A spreadsheet with the same three columns is fine too. Include questions it should refuse.
On a website
On a website, your pages reach the assistant under a path such as /assistant-api/. The
assistant runs either on our servers or on yours; the chat and the API are the same.
Hosted by NativeSoft
We run your assistant on our servers and give you its address. You add the chat to your pages, or
call the API from your own interface, and we keep it running and current. The model never leaves
our machines. Pass it through your own web server, so your pages call their own domain:
location /assistant-api/ {
proxy_pass https://<your assistant's address>/;
}
On your server
You receive the assistant server, your assistant as a sealed file (.ainx), the library
that opens it (libainx.so, made for you only) and your licence file
(license.txt), plus libainative_kernels.so (the 8-bit maths that make it
fast). Keep them in one folder. It needs:
- Linux on x86-64 (a CPU with AVX2). No GPU, no database, no outside service.
- About 100 to 160 MB of memory for a worker with a search assistant like this site's; an answer
usually takes a few milliseconds (see Size, memory and speed).
- It listens on
127.0.0.1 only. Put your web server in front of it.
ainative-serve --bundle assistant.ainx --port 3024 --workers 2
The server opens the sealed file in memory, with the library and the licence next to it. Without a
valid licence it does not start, and it says why: no licence, a changed one, one that has ended, or
one made for another machine. A licence can name an end date and the machine it runs on; a renewed
licence is a new file, the model stays the same.
| Option | Default | What it does |
--port | 3008 | The port to listen on |
--host | 127.0.0.1 | The address to listen on |
--workers | 4 | Questions answered at the same time,
each with its own copy of the model |
--queue | 16 | Questions that may wait; beyond
that the answer is 503 |
--cooldown-ms | 500 | The shortest time between two
questions of the same conversation; sooner is 429 |
--max-chars | 300 | Questions are cut at this
length |
--license | license.txt next to the server | Your
licence file (or the AINX_LICENSE environment variable) |
--seal-lib | libainx.so next to the server | The
library that opens your sealed file |
Behind Caddy:
example.com {
handle_path /assistant-api/* {
reverse_proxy 127.0.0.1:3024
}
# ... your site
}
Behind nginx:
location /assistant-api/ {
proxy_pass http://127.0.0.1:3024/;
}
Run it under your process manager (systemd, pm2 ...) so it starts with the machine. It is ready a
few seconds after it starts: curl 127.0.0.1:3024/health.
The chat on your pages
The ready-made chat is one script and one stylesheet. It adds a button at the bottom of the page and
a panel with the conversation. A link in an answer scrolls to the paragraph it came from and
highlights it. The button appears only when the assistant answers /health, so your
pages never wait for it and work without it.
<link rel="stylesheet" href="/assistant-chat.css">
<script src="/assistant-chat.js" defer
data-name="Example Assistant"
data-title="Ask about Example"
data-greeting="Hi! Ask me anything about Example."
data-placeholder="How do I start?"></script>
| Attribute | What it does |
data-api | Where the assistant is (default /assistant-api) |
data-lang | The page's language (default: <html lang>) |
data-name, data-title, data-greeting,
data-placeholder, data-button | The texts of the
chat |
data-open | true: open the panel on the first
visit |
data-base | Your site's address, taken off the links so they stay on
the current host (useful on staging) |
data-unavailable | What it says when the assistant cannot be
reached |
data-label-* | The labels of the panel in another language:
question, clear, restart, close,
more, also, wait |
The conversation is kept in the browser tab (sessionStorage), so follow-up questions
("and on Android?") work across pages. Nothing is tracked.
HTTP API
For your own interface, call the server directly. All requests and replies are JSON.
GET /health: whether the assistant is ready.
{"ok": true, "name": "Example Assistant", "langs": ["en", "ro"], "busy": 0, "queued": 0}
POST /ask: a question.
{
"q": "How do I reset my password?",
"session": "b7f3c2e1", // one id per conversation, made by your page
"page_lang": "en", // the page's language: used when the question is too short to tell
"links": "show", // none | show | open
"session_state": { ... } // optional: from the previous reply, for follow-ups
}
The reply is described in The reply. Pass its session_state back
with the next question of the same conversation. The server also remembers it for a while by
session, but sending it back keeps follow-ups working across restarts and
servers.
| Status | Meaning | What to do |
200 | The answer | Show it |
400 | No question (empty after cleaning) | Ask the user to type
a question |
429 | The same conversation asked again too soon
(retry_ms says how long to wait) | Wait, then send it again |
503 | All workers busy and the queue full | Wait a little, then
send it again |
Questions are plain text: control and invisible characters are removed, and anything over
--max-chars is cut. With "stream": true, an assistant that writes its
answers in its own words sends them as they are written, one JSON object per line
({"partial": "..."}), and the full reply last, with "final": true.
In a mobile or desktop app
In an app, the assistant runs on the user's device: the engine is a Dart package
(ainative_core, no plugins), and your assistant is a sealed model file inside the
app. It answers without a network and needs no permissions. The model is loaded once, in the
background, when the app starts; see Size, memory and speed for what it
takes. It is in production in Flutter apps on Android.
You receive, with the licence:
- the
ainative_core package;
- your assistant as a sealed model file (
.ainx);
libainx.so, the library that opens it, in memory only, built for your app's id: for
Android (arm64-v8a, armeabi-v7a, x86_64) and for Linux
desktop. It opens the model only inside your app: copied into another app, it refuses.
Put the libraries where the platform loads them, and add the package and the model to
pubspec.yaml:
android/app/src/main/jniLibs/arm64-v8a/libainx.so
android/app/src/main/jniLibs/armeabi-v7a/libainx.so
android/app/src/main/jniLibs/x86_64/libainx.so
assets/assistant/your_assistant.ainx
dependencies:
ainative_core:
path: third_party/ainative_core # as delivered
flutter:
assets:
- assets/assistant/
Load the model in its own isolate, so your screens never wait for it, and keep one session per
conversation:
import 'dart:isolate';
import 'dart:typed_data';
import 'package:ainative_core/ainative_core.dart';
void assistantMain(List args) {
final ready = args[0] as SendPort;
// opened by libainx.so, in this isolate's memory only
final ai = AinbBundle.parse(openSealedModel(args[1] as Uint8List)).assistant();
final sessions = <String, AssistantSession>{};
final inbox = ReceivePort();
ready.send(inbox.sendPort);
inbox.listen((m) {
final [q, sid, out] = m as List;
final r = ai.ask(cleanQuestion(q as String),
session: sessions.putIfAbsent(sid as String, AssistantSession.new),
host: const HostCapabilities({'open_url', 'open_menu'}),
links: LinkMode.show);
(out as SendPort).send({
'text': r.text,
'answered': r.answered,
'lang': r.lang,
if (r.link != null) 'link': {'title': r.link!.title, 'url': r.link!.url, 'route': r.link!.route},
});
});
}
If the model does not open, openSealedModel throws a
SealedModelException with the reason. Start the isolate when the app starts, with the model's bytes from
rootBundle.load('assets/assistant/your_assistant.ainx'), and send it each question with
the conversation's id and a SendPort for the reply. HostCapabilities
tells the assistant what your app can do: open_url (open a page) and
open_menu (open a screen of the app, for actions).
Release builds
The licence requires that a release of your app:
- ships the model only as the sealed file we deliver, with our
libainx.so, never
unsealed;
- is built with obfuscation, for example
flutter build appbundle --release --obfuscate
--split-debug-info=<folder> (keep that folder: it reads crash reports);
- shows the NativeSoft licence notice in its About, Info or Settings screen.
Obfuscation changes the names of classes, so code that compares runtimeType.toString()
stops working. enum.name is safe. Try the assistant's flows on the release build.
The reply
The same fields come from the HTTP API and from ai.ask() in the app:
| Field | What it is |
text | The answer to show |
answered | false when it declined: the question is
outside your content |
lang | The language of the answer |
source | extractive (sentences from your content),
generated (its own words, checked against your content), refusal,
or system (greetings, thanks, small talk) |
link | Where the answer came from: title,
url (a page, with the section's anchor) and/or route (a screen of
your app, menu:<key> for an action) |
more, more_text | Up to two related sections, and the
sentence to introduce them |
unsure | true when it is not sure it understood and shows
what comes closest |
example | A code example from your content: code and
language |
talk | The kind of small talk, when it is one (greeting, thanks
...) |
session_state | HTTP only: send it back with the next question |
For an action, read the key from link.route: menu:statement means "open
Statements". Your app then fills in the details and asks the user to confirm.
{
"text": "I can prepare the statement of any month in your history ...",
"answered": true,
"lang": "en",
"source": "extractive",
"link": {"title": "Account statements", "url": null, "route": "menu:statement"}
}
How the model is protected
When the assistant runs on your machines or your users' devices, it is delivered sealed:
- The assistant file is encrypted and authenticated: changing one byte, or the
NativeSoft notice at its start, makes it refuse to open.
- It is opened only in memory, by a library made for you alone. You never
receive a key, and the library of another licence does not open your file.
- In an app, the library opens it only inside the app it was made for. On a
server, only with your signed licence file.
No protection can make a model that runs on someone's machine impossible to study. Sealing makes
copying it slow and costly; the licence makes it unlawful. If you need the model to stay entirely
with us, choose hosting by NativeSoft.
Keeping it current
- Hosted by NativeSoft, your content is read again on a schedule (every night
or every week) or when you ask, and the index is rebuilt. If no section changed, nothing is
rebuilt. A scan that finds much less than before is not used: the previous version stays, and
we look at it.
- On your server, we send you a new sealed file when your content changes. Replace
the old one and restart the server; the library and the licence stay.
- In an app, a content update is a new sealed file in your next release.
- A new version of the model means rebuilding the index from your content. We
tell you before, and you test it before it replaces the old one.
Licence
A licence covers one assistant for one product: its website or its app, hosted by us or run by you.
The model, the engine, the file format and the index are NativeSoft's. Your content stays yours.
On your server, the licence is also a file the server reads at start. It can name an end date and
the machine; renewing it means a new file, not a new model.
The licence does not allow:
- extracting, unsealing, decompiling or reverse engineering the model, the engine or the
library that opens the model;
- using the model, or its answers in bulk, to build or train another product or model;
- removing the NativeSoft notice from the model file or from the app;
- using the assistant for another product than the licensed one.
Terms, prices and what fits your product: write to us.