Skip to main content

RawIntent 5.2.1

Make a small API with a few lines of Python.

Start here

Install this local version with python -m pip install . from the project folder.

Train a tiny language model

Install the optional machine-learning dependency with python -m pip install '.[ml]'. This educational character model starts with random weights and practices on your own UTF-8 text. It is small and does not have the knowledge or quality of a large pretrained assistant. A longer text file and more practice rounds take more time.

Save this as tiny_llm.py next to corpus.txt:

from rawintent import AIModel

AIModel = AIModel()

@AIModel.TRAIN(TRAINFILE="corpus.txt", MODELFILE="my_model.pt", STEPS=1000)
def train_model():
    print("Training finished")

@AIModel.PROMPT(SYSTEM_PROMPT="You are a helpful assistant.")
def ask(prompt):
    pass

train_model()
print(ask("Hello", tokens=80))

STEPS means practice rounds: each round shows the model short pieces of your text and asks it to guess the next character. CONTEXT controls how many characters it sees at once. Training creates a checkpoint file; the prompt decorator can load it again on a later run.

1. Make something people can read

Save this as app.py:

from rawintent import API

api = API()
api.reply("hello", "Hello, world!", public=True)
api.start()

Run python app.py. Open http://127.0.0.1:8000/hello in your browser. You will see "Hello, world!". Keep the program running while you use the API.

  • API() makes your API.
  • reply("hello", ...) puts an answer at /hello.
  • public=True means people can read it without a key.
  • start() starts the server.

2. Make something people can ask to do

Replace app.py with:

from rawintent import API

api = API()

@api.action(public=True)
def add(a=0, b=0):
    return a + b

api.start()

Stop the previous server with Ctrl+C, then run python app.py again. The function name add becomes the address /add. a=0 and b=0 mean these inputs are whole numbers and default to zero. Use 0.0 for decimals, "" for text, or False for yes/no values. Explicit type annotations still work when you want them.

In another terminal, run a second Python file:

from rawintent import send

answer = send("http://127.0.0.1:8000/add", a=2, b=3)
print(answer)  # 5

That's it: give send() an address and the values to send. It handles the JSON request and closes its connection for you. Use read(address) for fixed replies and other GET endpoints. Supply query values as read(address, name="Alice"). The address must include http:// or https://; pass inputs as named values, not as a query string in the address.

3. Add a key when you want private access

Change @api.action(public=True) to just @api.action, then restart the server. It now requires a key. In a separate file, run this once from the same folder where you started your server:

from rawintent import make_key

print(make_key())

Copy the printed key. Then send it with your request:

from rawintent import send

answer = send("http://127.0.0.1:8000/add", key="PASTE_YOUR_KEY_HERE", a=2, b=3)
print(answer)

Keep your real key private; an environment variable is a good place to keep it when sharing your code. examples/easy_client.py shows that approach. Create a key once and reuse it; each call to make_key() creates a different key. If you set API(key_store=...), use the same path in make_key(store=...).

Run and try your API in one file

For a quick demo or a test, use api.running(). It starts a real local server, waits until it is ready, and stops it when you leave the block:

from rawintent import API

api = API(key_store=":memory:")

@api.action
def multiply(a=0, b=0):
    return a * b

key = api.create_key("example")

with api.running(key=key) as server:
    print(server.send("multiply", a=6, b=7))  # 42

There is no need to write socket, thread, waiting, or shutdown code. server.send("name", ...) calls an action. server.read("name", ...) reads an endpoint. server.url gives the address if you want to use a different client. A free port is selected automatically. Set port=8000 if you need a fixed port.

The key is optional for public endpoints. Passing a key does not bypass permissions, expiration, or revocation. This example uses an in-memory key store so its demo key disappears when the program ends; normal apps use the default persistent store. Each running block starts a new server; a finished context cannot be re-entered.

api.running(timeout=10) bounds startup waiting, request timeouts, and graceful shutdown. Async startup can be cancelled on timeout; blocking user code cannot be forcibly terminated in a Python thread. A shutdown that cannot finish raises a timeout error. This helper listens on local loopback and serves HTTP endpoints; use api.start() or the ASGI server commands for normal hosting or WebSockets.

Handy shortcuts

api.reply("status", {"running": True}, public=True)

@api.action(name="say-hello", public=True)
def greet(name="friend"):
    return f"Hello, {name}!"

Use send("http://127.0.0.1:8000/say-hello", name="Sam") to call it. Visit http://127.0.0.1:8000/docs to try your API in a browser.

For several requests, reuse a client:

from rawintent import Client

with Client("http://127.0.0.1:8000", api_key="YOUR_KEY") as client:
    print(client.call("add", a=2, b=3))

AsyncClient also supports await client.call(...). send() and read() reserve the argument key for authentication; use the existing Client.post(..., json={...}) or Client.get(..., params={...}) interface if your endpoint itself has an input named key.

These helpers are additions: the 5.0 endpoint decorators, clients, and key management still work. The full API reference follows.

Install the optional dependencies with python -m pip install '.[onion-search]'. Run Tor or Tor Browser locally, then search through its SOCKS proxy:

from rawintent import OnionSearcher

with OnionSearcher() as searcher:
    for result in searcher.search("example topic", limit=5):
        print(result.title, result.url, result.description)

The default proxy is socks5h://127.0.0.1:9150 (Tor Browser); set proxy="socks5h://127.0.0.1:9050" for a typical Tor service. The socks5h scheme resolves onion hostnames through Tor. You can also run rawintent-onion-search "example topic" --limit 5; use --json for JSON output. The default engine is configurable with engine_url, and must be a v3 .onion URL. The library returns search-engine results only; it does not visit result sites. Onion services and search engines may be unavailable or return unsafe, misleading, or outdated content. Use Tor only in ways allowed by local law.


Build your own API: define endpoints, create API keys, and send requests.

Version 5 replaces the English-language engine and neural compiler completely. It is a breaking redesign, not a compatible update to RawIntent 4. Pin rawintent==4.1.0 if you still need the old language engine.

Install

pip install rawintent

For an unpublished local checkout, use python -m pip install .. Python 3.10 or later is required.

Create a server

Save this as app.py:

from rawintent import API

api = API("My API")

@api.get("/hello")
def hello(name: str = "world"):
    return {"message": f"Hello, {name}!"}

@api.post("/add")
def add(a: float, b: float):
    return {"result": a + b}

@api.get("/health", public=True)
def health():
    return {"status": "ok"}

if __name__ == "__main__":
    api.run()

Run python app.py. It listens at http://127.0.0.1:8000. Open http://127.0.0.1:8000/docs to try endpoints in your browser.

You can also generate the starter and launch it with:

rawintent init app.py
rawintent serve app:api --reload

python -m rawintent works anywhere the console command is unavailable. init will not overwrite an existing file.

Create a key and send requests

Run this in another terminal from the same working directory as your server:

rawintent keys create --name my-client

Copy the returned ri_... token. It is displayed only at creation and cannot be recovered from the database. Put it in an environment variable or a secret store. In PowerShell you can capture the key directly:

$env:RAWINTENT_API_KEY = python -m rawintent keys create --name my-client
python examples/client.py

In your client script:

import os
from rawintent import Client

with Client("http://127.0.0.1:8000", api_key=os.environ["RAWINTENT_API_KEY"]) as client:
    print(client.get("/hello", params={"name": "Vincent"}))
    print(client.post("/add", json={"a": 2, "b": 3}))

Output:

{'message': 'Hello, Vincent!'}
{'result': 5.0}

Any HTTP client can call your API. Send the key in the X-API-Key header. The interactive docs have an Authorize button for the same key. Keys belong to the server/database that issued them; they are not PyPI tokens or credentials for a hosted RawIntent service.

Endpoint rules

  • @api.get, @api.post, @api.put, @api.patch, and @api.delete register routes.
  • Endpoints require a valid key unless public=True is explicit.
  • GET and DELETE scalar arguments come from query parameters.
  • POST, PUT, and PATCH arguments become fields of a JSON object.
  • {item_id} in a route is a path parameter and takes precedence over the body.
  • Python type annotations validate inputs. Missing or invalid input returns HTTP 422.
  • Both regular def and async def handlers work.
  • Dictionary and list results become JSON. Return Response for other formats.
  • @api.route("/status", methods=["GET", "HEAD"]) supports other HTTP methods.
  • Register body-based and query-based methods separately.
@api.put("/items/{item_id}")
def update_item(item_id: int, title: str, count: int = 1):
    return {"id": item_id, "title": title, "count": count}

Call with client.put("/items/42", json={"title": "Notebook", "count": 3}).

For a raw JSON body or explicit query arguments, use exported FastAPI helpers:

from rawintent import Body, Query

@api.post("/echo")
def echo(data: dict = Body(...), verbose: bool = Query(False)):
    return {"data": data, "verbose": verbose}

Body(...) uses the entire JSON document. Ordinary parameters instead use named fields, so def echo(data: dict) expects {"data": {...}}. BaseModel, Field, Depends, Request, Response, and HTTPException are also exported. See FastAPI parameter documentation for explicit parameter declarations and nested Pydantic models.

Key management

The default SQLite registry is .rawintent/keys.sqlite3, relative to the process working directory. Use an absolute path for servers launched from different directories:

api = API("My API", key_store="C:/my-api/keys.sqlite3")

Use the same file with CLI commands:

rawintent keys create --store C:/my-api/keys.sqlite3 --name reader --scope items:read --expires-in 86400
rawintent keys list --store C:/my-api/keys.sqlite3
rawintent keys revoke KEY_ID --store C:/my-api/keys.sqlite3

keys list shows IDs, names, prefixes, scopes, expiration, and active status; it never prints secret tokens. Revocation takes effect on the next authentication check, including across server processes sharing the same SQLite file.

Create keys programmatically with api.create_key("client-name"), or use KeyStore(path).create(...). Create them during provisioning, not each time an application imports or a development server reloads.

For a local app that should save and reuse one client key automatically, call api.load_or_create_key(...) before starting the server:

api = API(key_store=".rawintent/keys.sqlite3")
key = api.load_or_create_key(".rawintent/client-key.txt", name="local-client")
api.start()

The helper creates the key in the API's key store if the file is missing, then returns the saved key on later runs. The key file contains the secret in plain text; keep it private and out of source control.

@api.get("/items", scopes=["items:read"])
def list_items():
    return []

A key created with scopes=["items:read"] can access that endpoint. Missing required scopes return 403. Keys with scopes=None (the default) have all scopes; scopes=[] allows ordinary protected endpoints but no named scopes. Scope names are exact strings, with no wildcard expansion. Expired or revoked keys return 401. request.state.api_key contains authenticated KeyInfo inside a protected handler.

Async clients and errors

import asyncio
import os
from rawintent import AsyncClient, APIError

async def main():
    async with AsyncClient("http://127.0.0.1:8000", os.environ["RAWINTENT_API_KEY"]) as client:
        try:
            print(await client.post("/add", json={"a": 2, "b": 3}))
        except APIError as error:
            print(error.status_code, error.detail)

asyncio.run(main())

Clients parse JSON responses, return text for non-JSON responses, and return None for empty responses. HTTP errors and redirects raise APIError. Network errors and timeouts retain their HTTPX exception types. The default timeout is 30 seconds; set timeout=10 when constructing a client to change it. Writes are not retried. Client requests accept paths on their configured server, not arbitrary external URLs. Redirects are not followed. A base URL ending in /v1 retains that prefix for calls such as client.get("/items").

Hosting

RawIntent builds on FastAPI, Uvicorn, and HTTPX. api is an ASGI application; run rawintent serve app:api --host 0.0.0.0 --port 8000 or uvicorn app:api on your host. Use a TLS reverse proxy for HTTPS outside local development. Disable interactive docs with API(docs=False) if desired. Docs and the OpenAPI schema are public when enabled, but protected endpoint calls still require a key. api.app exposes the underlying FastAPI app for advanced middleware and lifespan configuration. Routes added directly to api.app use FastAPI rules and do not automatically get RawIntent key authentication.

The SQLite registry must live on persistent storage. Keep it and your tokens out of source control. The library does not provide hosting, billing, user accounts, rate limiting, or a public key-signup portal. Key creation is a local administrative operation; there is no unauthenticated web endpoint that issues credentials.

Development

python -m pip install -e ".[dev]"
python -m pytest -q
python -m build
python -m twine check dist/*

Metadata

Release files for rawintent 5.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rawintent 5.2.1
File Size Uploaded
rawintent-5.2.1.tar.gz 35.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rawintent 5.2.1
File Interpreter ABI Platform
rawintent-5.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 61.3 kB

Release files / rawintent-5.2.1.tar.gz

Download URL rawintent-5.2.1.tar.gz
Size 35.3 kB
Tags Source
SHA-256 checksum
How to use checksums
dd202ab252b3ae5f9b1eec2c0cab5e0531971951ec7d71307144719e0c8740ab
BLAKE2b-256 checksum
How to use checksums
204a204b6dfd3be563fe204a865c444c3bc470b8b35d271ae3cfd5c11b41e80c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release files / rawintent-5.2.1-py3-none-any.whl

Download URL rawintent-5.2.1-py3-none-any.whl
Size 26.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
88110409d3ec53b63b2e06b326d92a99567e17d97693dba3a6775597b151ee20
BLAKE2b-256 checksum
How to use checksums
e8f8d078c33699a18071636d87908aea47a1e029a7743494b58dbf9f11b0cf0a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release history Release notifications | RSS feed

5.2.2

2 release files

This release

5.2.1 This release

2 release files

5.2.0

2 release files

5.1.1

2 release files

5.1.0

2 release files

5.0.0

2 release files

4.1.0

2 release files

4.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page