> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pylonsync.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Search & AI

> Full-text search (FTS5 + facets) and the built-in LLM proxy. Vector search and MCP are on the roadmap.

Two capabilities ship today: **full-text search** (configured per-entity) and a **built-in LLM proxy** (configured by env). Neither is enabled through a `manifest.plugins` list.

## `search`

Full-text search backed by SQLite's FTS5 (or Postgres `tsvector` on Postgres). Configured on the **entity**, not as a plugin — the presence of a `search` config creates FTS5 + facet-bitmap shadow tables on schema push, maintained on every write.

Declare it with the fluent builder's `.search({...})` (or the `search` key in the entity options):

```ts theme={null}
import { e, field } from "@pylonsync/sdk";

export const Post = e.entity("Post", {
  title:    field.string(),
  body:     field.string(),
  tags:     field.string(),
  authorId: field.string(),
  viewCount: field.int(),
}).search({
  text:     ["title", "body"],   // tokenized into the FTS5 index
  facets:   ["authorId", "tags"], // materialized as facet bitmaps
  sortable: ["viewCount"],        // indexed for stable pagination
});
```

| Key        | Purpose                                                     |
| ---------- | ----------------------------------------------------------- |
| `text`     | Fields tokenized into the FTS5 index for keyword matching   |
| `facets`   | Fields materialized as bitmaps for instant per-value counts |
| `sortable` | Fields indexed so result sets paginate stably               |

Query via `POST /api/search/<Entity>`, or the React hook:

```tsx theme={null}
const { hits, facetCounts, total, loading } = db.useSearch<Post>("Post", {
  query: input,
  facets: ["tags"],
});
```

`facetCounts` is the live count per facet value over the current filtered hit set — enough to build Algolia-style faceted UIs without Algolia.

## LLM proxy

A built-in proxy to LLM providers so your API key stays server-side and clients never see it. It's configured by **environment variables**, not a plugin entry.

Two endpoints, both requiring an authenticated session:

* `POST /api/llm/complete` — non-streaming completion.
* `POST /api/ai/stream` — SSE streaming completion.

And, inside a server **action**, `ctx.llm.complete(...)` (server-only — the key never reaches the client; not available in queries).

### Env

```bash theme={null}
# /api/llm/complete and ctx.llm.complete
PYLON_LLM_PROVIDER=anthropic          # anthropic | openai
ANTHROPIC_API_KEY=sk-ant-...          # or OPENAI_API_KEY for openai

# /api/ai/stream (SSE)
PYLON_AI_PROVIDER=anthropic           # anthropic | openai | custom
PYLON_AI_API_KEY=sk-ant-...
PYLON_AI_MODEL=claude-sonnet-4-5
PYLON_AI_BASE_URL=https://...         # required for provider "custom"
```

### Model allowlist

Clients may request a specific model on `/api/ai/stream` only if it's allowlisted — otherwise the request is rejected with `MODEL_NOT_ALLOWED`. Set the allowlist via the manifest `llm()` helper or `PYLON_AI_MODELS_ALLOWED`:

```ts theme={null}
import { buildManifest, llm } from "@pylonsync/sdk";

buildManifest({
  name: "myapp",
  llm: llm({ allowedModels: ["claude-sonnet-4-5", "claude-haiku-4-5"] }),
  // ...
});
```

Why proxy:

1. **API keys stay server-side** — browser / native clients never see them.
2. **Auth-gated** — both endpoints require a session.
3. **Model allowlist** — cap which models clients can invoke.

## Not yet built in

The roadmap catalog (`pylon plugins list`) names a few AI capabilities that are **not** implemented as built-ins today:

* **Vector / semantic search** — Pylon has no built-in embedding index. For RAG or similarity today, compute embeddings in a server function and store/query them yourself (a dedicated store like pgvector or Qdrant, or a table you scan), or wait for the built-in.
* **MCP server** — exposing your entities/functions as Model Context Protocol tools is planned, not shipped.

Don't build against these until they land.
