mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 04:23:15 -05:00
feat(collections): show how a search finds passages
The collections list now reports search_mode: semantic for the chromem engine and hybrid for the postgres engine, which also matches words. The Knowledge page shows it in plain words, so a person knows whether an exact code or name can be found. Assisted-by: Claude:claude-sonnet-5-5
This commit is contained in:
19 files changed
+149
-5
No files matched your search
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// ListCollectionsEndpoint handles /api/collections (GET).
|
||||
// @Summary List your collections.
|
||||
// @Description The knowledge-base collections of the caller. An administrator can add all_users=true to also get the other users' collections in user_groups. embedding_model names the model that indexes and searches the collections (collections_embedding_model), and embedding_model_installed says whether this server has it: without it, adding files and searching fail. Needs the collections feature.
|
||||
// @Description The knowledge-base collections of the caller. An administrator can add all_users=true to also get the other users' collections in user_groups. embedding_model names the model that indexes and searches the collections (collections_embedding_model), and embedding_model_installed says whether this server has it: without it, adding files and searching fail. search_mode says how a search finds passages: semantic (by meaning only) or hybrid (by meaning and by keyword). Needs the collections feature.
|
||||
// @Tags collections
|
||||
// @Success 200 {object} schema.CollectionList "The collections"
|
||||
// @Router /api/collections [get]
|
||||
@@ -30,6 +30,7 @@ func ListCollectionsEndpoint(app *application.Application) echo.HandlerFunc {
|
||||
"count": len(cols),
|
||||
"embedding_model": model,
|
||||
"embedding_model_installed": embeddingModelInstalled(app, model),
|
||||
"search_mode": collectionSearchMode(app.ApplicationConfig().Collections.VectorEngine),
|
||||
}
|
||||
|
||||
// Admin cross-user aggregation
|
||||
@@ -57,6 +58,16 @@ func ListCollectionsEndpoint(app *application.Application) echo.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// collectionSearchMode says how a search of the vector engine finds passages.
|
||||
// The chromem and localai engines compare embeddings only. The postgres engine
|
||||
// also matches words (BM25) and mixes the two lists.
|
||||
func collectionSearchMode(engine string) string {
|
||||
if engine == "postgres" {
|
||||
return "hybrid"
|
||||
}
|
||||
return "semantic"
|
||||
}
|
||||
|
||||
// CreateCollectionEndpoint handles /api/collections (POST).
|
||||
// @Summary Create a collection.
|
||||
// @Description Makes an empty collection under the name in the body.
|
||||
|
||||
@@ -48,3 +48,15 @@ var _ = Describe("the embedding model of the collections", func() {
|
||||
Expect(embeddingModelInstalled(app, "plain.gguf")).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("the search mode of the collections", func() {
|
||||
It("is semantic for the engines that compare embeddings only", func() {
|
||||
Expect(collectionSearchMode("chromem")).To(Equal("semantic"))
|
||||
Expect(collectionSearchMode("localai")).To(Equal("semantic"))
|
||||
Expect(collectionSearchMode("")).To(Equal("semantic"))
|
||||
})
|
||||
|
||||
It("is hybrid for the postgres engine, which also matches words", func() {
|
||||
Expect(collectionSearchMode("postgres")).To(Equal("hybrid"))
|
||||
})
|
||||
})
|
||||
@@ -27,6 +27,30 @@ test.describe('Collections page', () => {
|
||||
await expect(page.getByRole('button', { name: 'New collection' }).first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('says how a search finds passages, in plain words', async ({ page }) => {
|
||||
const list = mode => route => route.fulfill({ contentType: 'application/json', body: JSON.stringify({ collections: ['docs'], search_mode: mode, embedding_model_installed: true }) })
|
||||
await page.route(/\/api\/collections(\?.*)?$/, list('semantic'))
|
||||
await page.goto('/app/collections')
|
||||
await expect(page.getByTestId('search-mode')).toContainText('Search: semantic.')
|
||||
await expect(page.getByTestId('search-mode')).toContainText('by meaning, not by exact words or codes')
|
||||
|
||||
await page.unroute(/\/api\/collections(\?.*)?$/)
|
||||
await page.route(/\/api\/collections(\?.*)?$/, list('hybrid'))
|
||||
await page.goto('/app/collections')
|
||||
await expect(page.getByTestId('search-mode')).toContainText('Search: hybrid.')
|
||||
await expect(page.getByTestId('search-mode')).toContainText('by meaning and by exact words')
|
||||
|
||||
await page.setViewportSize({ width: 360, height: 740 })
|
||||
await expect(page.getByTestId('search-mode')).toBeVisible()
|
||||
// The page animates in; wait for the layout to settle before measuring.
|
||||
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth)).toBeLessThanOrEqual(0)
|
||||
})
|
||||
|
||||
test('says nothing about the search mode when the server does not report it', async ({ page }) => {
|
||||
await expect(page.getByTestId('memory-empty')).toBeVisible()
|
||||
await expect(page.getByTestId('search-mode')).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('new-collection name field accepts input', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'New collection' }).first().click()
|
||||
const input = page.getByLabel('New collection name')
|
||||
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "Für Sammlungen ist kein Embedding-Modell festgelegt (collections_embedding_model), daher schlagen das Hinzufügen von Dateien und die Suche fehl.",
|
||||
"install": "In Modelle suchen",
|
||||
"askAdmin": "Bitte eine Administratorin oder einen Administrator, es zu installieren."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "Suche: {{mode}}",
|
||||
"semantic": "semantisch",
|
||||
"hybrid": "hybrid",
|
||||
"semanticHint": "Sie findet Textstellen nach Bedeutung, nicht nach genauen Wörtern oder Codes.",
|
||||
"hybridHint": "Sie findet Textstellen nach Bedeutung und nach genauen Wörtern."
|
||||
}
|
||||
}
|
||||
@@ -146,5 +146,12 @@
|
||||
"noneSet": "No embedding model is set for collections (collections_embedding_model), so adding files and searching fail.",
|
||||
"install": "Find it in Models",
|
||||
"askAdmin": "Ask an administrator to install it."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "Search: {{mode}}",
|
||||
"semantic": "semantic",
|
||||
"hybrid": "hybrid",
|
||||
"semanticHint": "It finds passages by meaning, not by exact words or codes.",
|
||||
"hybridHint": "It finds passages by meaning and by exact words."
|
||||
}
|
||||
}
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "No hay ningún modelo de embeddings configurado para las colecciones (collections_embedding_model), así que añadir archivos y buscar fallan.",
|
||||
"install": "Buscarlo en Modelos",
|
||||
"askAdmin": "Pide a un administrador que lo instale."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "Búsqueda: {{mode}}",
|
||||
"semantic": "semántica",
|
||||
"hybrid": "híbrida",
|
||||
"semanticHint": "Encuentra pasajes por su significado, no por palabras o códigos exactos.",
|
||||
"hybridHint": "Encuentra pasajes por su significado y por palabras exactas."
|
||||
}
|
||||
}
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "Aucun modèle d'embedding n'est défini pour les collections (collections_embedding_model) : l'ajout de fichiers et la recherche échouent.",
|
||||
"install": "Le trouver dans Modèles",
|
||||
"askAdmin": "Demandez à un administrateur de l'installer."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "Recherche : {{mode}}",
|
||||
"semantic": "sémantique",
|
||||
"hybrid": "hybride",
|
||||
"semanticHint": "Elle trouve des passages par le sens, pas par des mots ou des codes exacts.",
|
||||
"hybridHint": "Elle trouve des passages par le sens et par des mots exacts."
|
||||
}
|
||||
}
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "Tidak ada model embedding yang diatur untuk koleksi (collections_embedding_model), jadi menambah file dan pencarian gagal.",
|
||||
"install": "Cari di Model",
|
||||
"askAdmin": "Minta administrator untuk memasangnya."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "Pencarian: {{mode}}",
|
||||
"semantic": "semantik",
|
||||
"hybrid": "hibrida",
|
||||
"semanticHint": "Pencarian menemukan bagian teks berdasarkan makna, bukan kata atau kode yang persis.",
|
||||
"hybridHint": "Pencarian menemukan bagian teks berdasarkan makna dan kata yang persis."
|
||||
}
|
||||
}
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "Nessun modello di embedding è impostato per le raccolte (collections_embedding_model), quindi l'aggiunta di file e la ricerca non funzionano.",
|
||||
"install": "Trovalo in Modelli",
|
||||
"askAdmin": "Chiedi a un amministratore di installarlo."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "Ricerca: {{mode}}",
|
||||
"semantic": "semantica",
|
||||
"hybrid": "ibrida",
|
||||
"semanticHint": "Trova i passaggi per significato, non per parole o codici esatti.",
|
||||
"hybridHint": "Trova i passaggi per significato e per parole esatte."
|
||||
}
|
||||
}
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "컬렉션에 임베딩 모델이 설정되어 있지 않아(collections_embedding_model) 파일 추가와 검색이 실패합니다.",
|
||||
"install": "모델에서 찾기",
|
||||
"askAdmin": "관리자에게 설치를 요청하세요."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "검색: {{mode}}",
|
||||
"semantic": "의미 기반",
|
||||
"hybrid": "하이브리드",
|
||||
"semanticHint": "정확한 단어나 코드가 아니라 의미로 구절을 찾습니다.",
|
||||
"hybridHint": "의미와 정확한 단어로 구절을 찾습니다."
|
||||
}
|
||||
}
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "Nenhum modelo de embeddings está definido para as coleções (collections_embedding_model), então adicionar arquivos e pesquisar falham.",
|
||||
"install": "Encontrar em Modelos",
|
||||
"askAdmin": "Peça a um administrador para instalá-lo."
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "Busca: {{mode}}",
|
||||
"semantic": "semântica",
|
||||
"hybrid": "híbrida",
|
||||
"semanticHint": "Encontra trechos pelo significado, não por palavras ou códigos exatos.",
|
||||
"hybridHint": "Encontra trechos pelo significado e por palavras exatas."
|
||||
}
|
||||
}
|
||||
@@ -57,5 +57,12 @@
|
||||
"noneSet": "未为集合设置嵌入模型(collections_embedding_model),因此添加文件和搜索都会失败。",
|
||||
"install": "在模型中查找",
|
||||
"askAdmin": "请让管理员安装它。"
|
||||
},
|
||||
"searchMode": {
|
||||
"label": "搜索:{{mode}}",
|
||||
"semantic": "语义",
|
||||
"hybrid": "混合",
|
||||
"semanticHint": "按含义查找段落,不按精确的词或代码匹配。",
|
||||
"hybridHint": "按含义和精确的词查找段落。"
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,9 @@ export default function Collections() {
|
||||
// The model that indexes and searches the collections, and whether this
|
||||
// server has it. Without it every upload and search fails.
|
||||
const [embedding, setEmbedding] = useState({ model: '', installed: true })
|
||||
// How a search finds passages: 'semantic' or 'hybrid'. '' while unknown
|
||||
// (an older server does not say).
|
||||
const [searchMode, setSearchMode] = useState('')
|
||||
|
||||
const selectedUser = params.get('user_id') || undefined
|
||||
|
||||
@@ -74,6 +77,7 @@ export default function Collections() {
|
||||
setCollections((Array.isArray(data.collections) ? data.collections : []).map(nameOf).filter(Boolean))
|
||||
setUserGroups(data.user_groups || null)
|
||||
setEmbedding({ model: data.embedding_model || '', installed: data.embedding_model_installed !== false })
|
||||
setSearchMode(data.search_mode === 'hybrid' || data.search_mode === 'semantic' ? data.search_mode : '')
|
||||
setLoadError(null)
|
||||
} catch (err) {
|
||||
// A failed read shows an error in place of the list, never the empty
|
||||
@@ -257,6 +261,13 @@ export default function Collections() {
|
||||
</form>
|
||||
)}
|
||||
|
||||
{!loading && !loadError && searchMode && (
|
||||
<p className="lib-mode" data-testid="search-mode">
|
||||
<strong>{t('collections:searchMode.label', { mode: t(`collections:searchMode.${searchMode}`) })}.</strong>{' '}
|
||||
{t(`collections:searchMode.${searchMode}Hint`)}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!loading && !loadError && !embedding.installed && (
|
||||
<Callout
|
||||
tone="warn"
|
||||
|
||||
@@ -197,6 +197,8 @@ a.lib-chip__link:hover { text-decoration: underline; text-underline-offset: 3px;
|
||||
.lib-kv__pair { display: contents; }
|
||||
.lib-gap { margin-left: var(--dk-space-2); }
|
||||
.lib-fine { margin: 0; font-size: var(--fs-meta); }
|
||||
.lib-mode { margin: 0 0 var(--dk-space-3); font-size: var(--fs-small); color: var(--dk-muted); overflow-wrap: anywhere; }
|
||||
.lib-mode strong { color: var(--dk-text); font-weight: 600; }
|
||||
.lib-create--page { margin-bottom: var(--dk-space-4); max-width: 520px; }
|
||||
.lib-more { align-self: flex-start; background: none; border: 0; padding: 0; font: inherit; cursor: pointer; color: var(--dk-accent-text); text-decoration: underline; text-underline-offset: 3px; }
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ type CollectionList struct {
|
||||
// adding files and searching then fail until it is installed.
|
||||
EmbeddingModel string `json:"embedding_model"`
|
||||
EmbeddingModelInstalled bool `json:"embedding_model_installed"`
|
||||
// SearchMode is how a search finds passages: "semantic" (by meaning only)
|
||||
// or "hybrid" (by meaning and by keyword).
|
||||
SearchMode string `json:"search_mode"`
|
||||
// UserGroups holds the collections of the other users. Only an administrator
|
||||
// who asks for all users gets it.
|
||||
UserGroups map[string]CollectionGroup `json:"user_groups,omitempty"`
|
||||
|
||||
@@ -36,6 +36,10 @@ and names the model; an administrator gets a button that opens the Models
|
||||
page with a search for it. `GET /api/collections` reports the same in
|
||||
`embedding_model` and `embedding_model_installed`.
|
||||
|
||||
The Knowledge page also shows how a search finds passages, for example
|
||||
*Search: semantic*. `GET /api/collections` reports it in `search_mode`: `semantic`
|
||||
for the `chromem` engine, and `hybrid` for the `postgres` engine.
|
||||
|
||||
## Who sees a collection
|
||||
|
||||
- With authentication off, there is one set of collections.
|
||||
|
||||
+5
-1
@@ -11214,7 +11214,7 @@ const docTemplate = `{
|
||||
},
|
||||
"/api/collections": {
|
||||
"get": {
|
||||
"description": "The knowledge-base collections of the caller. An administrator can add all_users=true to also get the other users' collections in user_groups. embedding_model names the model that indexes and searches the collections (collections_embedding_model), and embedding_model_installed says whether this server has it: without it, adding files and searching fail. Needs the collections feature.",
|
||||
"description": "The knowledge-base collections of the caller. An administrator can add all_users=true to also get the other users' collections in user_groups. embedding_model names the model that indexes and searches the collections (collections_embedding_model), and embedding_model_installed says whether this server has it: without it, adding files and searching fail. search_mode says how a search finds passages: semantic (by meaning only) or hybrid (by meaning and by keyword). Needs the collections feature.",
|
||||
"tags": [
|
||||
"collections"
|
||||
],
|
||||
@@ -26173,6 +26173,10 @@ const docTemplate = `{
|
||||
"embedding_model_installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search_mode": {
|
||||
"description": "SearchMode is how a search finds passages: \"semantic\" (by meaning only)\nor \"hybrid\" (by meaning and by keyword).",
|
||||
"type": "string"
|
||||
},
|
||||
"user_groups": {
|
||||
"description": "UserGroups holds the collections of the other users. Only an administrator\nwho asks for all users gets it.",
|
||||
"type": "object",
|
||||
|
||||
@@ -11211,7 +11211,7 @@
|
||||
},
|
||||
"/api/collections": {
|
||||
"get": {
|
||||
"description": "The knowledge-base collections of the caller. An administrator can add all_users=true to also get the other users' collections in user_groups. embedding_model names the model that indexes and searches the collections (collections_embedding_model), and embedding_model_installed says whether this server has it: without it, adding files and searching fail. Needs the collections feature.",
|
||||
"description": "The knowledge-base collections of the caller. An administrator can add all_users=true to also get the other users' collections in user_groups. embedding_model names the model that indexes and searches the collections (collections_embedding_model), and embedding_model_installed says whether this server has it: without it, adding files and searching fail. search_mode says how a search finds passages: semantic (by meaning only) or hybrid (by meaning and by keyword). Needs the collections feature.",
|
||||
"tags": [
|
||||
"collections"
|
||||
],
|
||||
@@ -26170,6 +26170,10 @@
|
||||
"embedding_model_installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"search_mode": {
|
||||
"description": "SearchMode is how a search finds passages: \"semantic\" (by meaning only)\nor \"hybrid\" (by meaning and by keyword).",
|
||||
"type": "string"
|
||||
},
|
||||
"user_groups": {
|
||||
"description": "UserGroups holds the collections of the other users. Only an administrator\nwho asks for all users gets it.",
|
||||
"type": "object",
|
||||
|
||||
@@ -7941,6 +7941,11 @@ definitions:
|
||||
type: string
|
||||
embedding_model_installed:
|
||||
type: boolean
|
||||
search_mode:
|
||||
description: |-
|
||||
SearchMode is how a search finds passages: "semantic" (by meaning only)
|
||||
or "hybrid" (by meaning and by keyword).
|
||||
type: string
|
||||
user_groups:
|
||||
additionalProperties:
|
||||
$ref: '#/definitions/schema.CollectionGroup'
|
||||
@@ -19948,8 +19953,9 @@ paths:
|
||||
can add all_users=true to also get the other users'' collections in user_groups.
|
||||
embedding_model names the model that indexes and searches the collections
|
||||
(collections_embedding_model), and embedding_model_installed says whether
|
||||
this server has it: without it, adding files and searching fail. Needs the
|
||||
collections feature.'
|
||||
this server has it: without it, adding files and searching fail. search_mode
|
||||
says how a search finds passages: semantic (by meaning only) or hybrid (by
|
||||
meaning and by keyword). Needs the collections feature.'
|
||||
responses:
|
||||
"200":
|
||||
description: The collections
|
||||
|
||||
Reference in new issue
Block a user