@athenox/talos
Version:
Talos: a backend you describe instead of write. Declare a schema, write the rules in plain language, map URLs to sentences; a small built-in model turns them into query plans.
50 lines (37 loc) • 2 kB
Markdown
# Search and recommendations
All three rank the rows the plan chose, so the route's conditions, roles and
ownership still apply. They use Talos's own text embedder (shipped with the
model; French and English) for meaning, next to the words.
## Search
```ts
'GET /api/recipes': { say: 'show the recipes', shape: p => ({ search: p.q }) } // over `searchable`
'GET /api/articles': { say: 'show the articles', shape: p => ({ find: {
q: p.q, fields: { title: 3, excerpt: 2, body: 1 }, html: true, limit: 20, suggest: 'title' } }) }
```
`find` weighs fields (BM25), matches prefixes and one-letter typos, ignores
stop words and HTML, and falls back on meaning when no word matches.
`suggest: 'title'` turns it into autocompletion on the query's last word.
`find(rows, options)` is also exported, for rows you already have.
## Similar rows
```ts
'GET /api/recipes/:id/similar': { say: 'show the recipes', pin: false, limit: 4,
shape: p => ({ similar: { to: { id: Number(p.id) }, text: ['title', 'summary', 'ingredients'] } }) }
```
`to` is a row (`{ id }`, left out of the result) or a text. Words (TF-IDF) and
meaning, mixed 70/30.
## Recommendations
```ts
'GET /api/for-you': { say: 'show the recipes', guard: member, limit: 4, shape: (_, me) => ({ recommend: {
user: me.id,
interactions: { table: 'likes', user: 'user_id', item: 'recipe_id' }, // one row per (user, item)
text: ['title', 'summary'], date: 'created_at', halfLife: 30,
weights: { content: 1, together: 1, popular: 0.3, recent: 0.3 },
} }) }
```
Each candidate is scored by how close it is to what the reader already liked
(`content`), what readers who liked the same things liked (`together`),
popularity and recency. Items already seen are left out (`keepSeen: true` to
keep them); a visitor (`user: null`) gets popular and recent.
## Meaning, directly
`embedText(text)`, `cosine(a, b)`, `classify(text, { sport: 'match, stade', culture: 'concert' })`,
and the schema's `infer` (a column filled by meaning).