feat(settings): graph retrieval options in a graph source's retrieval settings

Exposes the three per-source graph options in the source's retrieval settings,
shown only when the retriever is graphrag: where the walk starts (entities or
relationships), whether passages join the walk, and whether vector hits are
blended in. Defaults match the backend's measured-best configuration and are
filled in for sources saved before the options existed. A note points at the
"search tool" exposure, which is what offers the graph to an agent.
This commit is contained in:
Alex committed 2026-09-19 14:07:41 +01:00
1 parent 6b9b193337
commit 5e67f8c927
10 files changed
+256

No files matched your search

+13
View File
@@ -268,6 +268,19 @@
},
"exposureHint": "Lade diese Quelle vorab in den Prompt oder lass den Agenten sie bei Bedarf als Werkzeug durchsuchen."
},
"graphRetrieval": {
"title": "Graph-Abruf",
"tag": "ohne Neuimport",
"seedStrategy": "Suche beginnt bei",
"seedStrategyHint": "Entitäten eignen sich für die meisten Dokumente. Beziehungen erreichen auch Entitäten, die in der Frage nicht vorkommen – ideal für Inhalte, die beschreiben, wie Dinge zusammenhängen.",
"seedEntities": "Entitäten (empfohlen)",
"seedRelationships": "Beziehungen",
"passageNodes": "Textabschnitte in die Suche einbeziehen",
"passageNodesHint": "Ein Abschnitt wird gefunden, wenn er zur Frage passt oder mit etwas Passendem verbunden ist. Am besten bei Graphen, die mit dieser Version erstellt wurden.",
"blendVector": "Mit Vektorsuche kombinieren",
"blendVectorHint": "Ergänzt Ergebnisse der Vektorsuche, damit kein Abschnitt verloren geht, den der Graph übersieht.",
"agentToolHint": "Agenten können diesen Beziehungen auch selbst folgen, wenn die Bereitstellung dieser Quelle „Suchwerkzeug auf Abruf“ ist oder ein agentischer Agent sie nutzt."
},
"prescreen": {
"enable": "LLM-Vorfilterung aktivieren",
"warning": "Ruft eine größere Kandidatenmenge ab und filtert sie mit einem LLM. Das erhöht Latenz und Kosten pro Anfrage.",
+13
View File
@@ -272,6 +272,19 @@
},
"exposureHint": "Pre-fetch this source into the prompt, or let the agent search it on demand as a tool."
},
"graphRetrieval": {
"title": "Graph retrieval",
"tag": "no re-ingest",
"seedStrategy": "Start the walk from",
"seedStrategyHint": "Entities suit most documents. Relationships can reach an entity the question never names, and suit content that describes how things connect.",
"seedEntities": "Entities (recommended)",
"seedRelationships": "Relationships",
"passageNodes": "Include passages in the walk",
"passageNodesHint": "Lets a passage be found both by matching the question and by being connected to what does. Works best on graphs built with this version.",
"blendVector": "Blend with vector search",
"blendVectorHint": "Adds plain vector search results, so a passage the graph misses is not lost.",
"agentToolHint": "Agents can also follow these relationships themselves when this source's exposure is “On-demand search tool”, or when an agentic agent uses it."
},
"prescreen": {
"enable": "Enable LLM prescreen",
"warning": "Fetches a larger candidate set and uses an LLM to filter it. This adds query-time latency and cost.",
+13
View File
@@ -268,6 +268,19 @@
},
"exposureHint": "Precarga esta fuente en el prompt, o deja que el agente la busque bajo demanda como herramienta."
},
"graphRetrieval": {
"title": "Recuperación por grafo",
"tag": "sin reingesta",
"seedStrategy": "Iniciar el recorrido desde",
"seedStrategyHint": "Las entidades funcionan para la mayoría de documentos. Las relaciones pueden llegar a una entidad que la pregunta no menciona; son ideales para contenido que describe cómo se conectan las cosas.",
"seedEntities": "Entidades (recomendado)",
"seedRelationships": "Relaciones",
"passageNodes": "Incluir fragmentos en el recorrido",
"passageNodesHint": "Un fragmento puede encontrarse por coincidir con la pregunta o por estar conectado con lo que coincide. Funciona mejor en grafos creados con esta versión.",
"blendVector": "Combinar con búsqueda vectorial",
"blendVectorHint": "Añade resultados de la búsqueda vectorial para no perder fragmentos que el grafo pase por alto.",
"agentToolHint": "Los agentes también pueden seguir estas relaciones por sí mismos cuando la exposición de esta fuente es «Herramienta de búsqueda bajo demanda» o cuando la usa un agente agéntico."
},
"prescreen": {
"enable": "Habilitar preselección con LLM",
"warning": "Obtiene un conjunto de candidatos más grande y usa un LLM para filtrarlo. Esto añade latencia y costo por consulta.",
+13
View File
@@ -268,6 +268,19 @@
},
"exposureHint": "このソースをプロンプトに事前取得するか、エージェントがツールとして必要に応じて検索できるようにします。"
},
"graphRetrieval": {
"title": "グラフ検索",
"tag": "再取り込み不要",
"seedStrategy": "探索の開始点",
"seedStrategyHint": "ほとんどのドキュメントにはエンティティが適しています。リレーションは質問に登場しないエンティティにも到達でき、物事のつながりを説明するコンテンツに向いています。",
"seedEntities": "エンティティ(推奨)",
"seedRelationships": "リレーション",
"passageNodes": "パッセージを探索に含める",
"passageNodesHint": "質問に一致するパッセージだけでなく、一致したものとつながるパッセージも見つけられます。このバージョン以降に構築したグラフで最も効果的です。",
"blendVector": "ベクトル検索と組み合わせる",
"blendVectorHint": "ベクトル検索の結果を加え、グラフが見落としたパッセージも失わないようにします。",
"agentToolHint": "このソースの公開方法が「オンデマンド検索ツール」の場合、またはエージェント型エージェントが使用する場合、エージェントはこれらのリレーションを自ら辿ることもできます。"
},
"prescreen": {
"enable": "LLMプリスクリーニングを有効にする",
"warning": "より多くの候補を取得し、LLMでフィルタリングします。クエリ時のレイテンシとコストが増加します。",
+13
View File
@@ -268,6 +268,19 @@
},
"exposureHint": "Предзагружать этот источник в промпт или позволить агенту искать по нему по мере необходимости как по инструменту."
},
"graphRetrieval": {
"title": "Поиск по графу",
"tag": "без повторной загрузки",
"seedStrategy": "Начинать обход с",
"seedStrategyHint": "Сущности подходят для большинства документов. Связи позволяют дойти до сущности, которая не упоминается в вопросе, — хорошо для контента о том, как всё связано.",
"seedEntities": "Сущностей (рекомендуется)",
"seedRelationships": "Связей",
"passageNodes": "Включать фрагменты в обход",
"passageNodesHint": "Фрагмент находится, если он соответствует вопросу или связан с тем, что соответствует. Лучше всего работает на графах, построенных в этой версии.",
"blendVector": "Сочетать с векторным поиском",
"blendVectorHint": "Добавляет результаты векторного поиска, чтобы не терять фрагменты, пропущенные графом.",
"agentToolHint": "Агенты также могут сами проходить по этим связям, если для источника выбран режим «Инструмент поиска по запросу» или его использует агентный агент."
},
"prescreen": {
"enable": "Включить предварительный отбор LLM",
"warning": "Извлекается расширенный набор кандидатов, который затем фильтруется с помощью LLM. Это увеличивает задержку и стоимость запроса.",
+13
View File
@@ -268,6 +268,19 @@
},
"exposureHint": "將此來源預先載入提示中,或讓代理以工具形式隨選搜尋。"
},
"graphRetrieval": {
"title": "圖譜檢索",
"tag": "無需重新匯入",
"seedStrategy": "走訪起點",
"seedStrategyHint": "實體適用於大多數文件。關係可以到達問題中未提及的實體,適合描述事物之間如何關聯的內容。",
"seedEntities": "實體(建議)",
"seedRelationships": "關係",
"passageNodes": "將段落納入走訪",
"passageNodesHint": "段落既可因符合問題而被找到,也可因與符合內容相連而被找到。在此版本之後建立的圖譜上效果最佳。",
"blendVector": "與向量檢索結合",
"blendVectorHint": "加入向量檢索結果,避免遺漏圖譜未找到的段落。",
"agentToolHint": "當此來源的公開方式為「隨選搜尋工具」,或由代理型代理使用時,代理也可以自行沿著這些關係查找。"
},
"prescreen": {
"enable": "啟用 LLM 預篩選",
"warning": "會擷取較大的候選集合並使用 LLM 篩選。這將增加查詢延遲與成本。",
+13
View File
@@ -268,6 +268,19 @@
},
"exposureHint": "将此来源预取到提示词中,或让代理按需将其作为工具进行搜索。"
},
"graphRetrieval": {
"title": "图谱检索",
"tag": "无需重新导入",
"seedStrategy": "遍历起点",
"seedStrategyHint": "实体适用于大多数文档。关系可以到达问题中未提及的实体,适合描述事物之间如何关联的内容。",
"seedEntities": "实体(推荐)",
"seedRelationships": "关系",
"passageNodes": "将段落纳入遍历",
"passageNodesHint": "段落既可因匹配问题被找到,也可因与匹配内容相连而被找到。在此版本之后构建的图谱上效果最佳。",
"blendVector": "与向量检索结合",
"blendVectorHint": "加入向量检索结果,避免遗漏图谱未找到的段落。",
"agentToolHint": "当此来源的公开方式为“按需搜索工具”,或由智能体型代理使用时,代理也可以自行沿这些关系查找。"
},
"prescreen": {
"enable": "启用 LLM 预筛选",
"warning": "会获取更大的候选集并使用 LLM 进行过滤。这会增加查询时的延迟和成本。",
+12
View File
@@ -32,6 +32,17 @@ export type SourcePrescreenConfig = {
max_keep?: number; // default 8, <= candidate_k
};
// Where the graph walk starts: matching entities, or matching relationships
// ("A streams_to B"), which can reach an entity the question never names.
export type GraphSeedStrategy = 'entities' | 'relationships';
// Query-time graph retrieval knobs (graphrag only; live, no re-ingest).
export type SourceGraphRetrievalConfig = {
seed_strategy?: GraphSeedStrategy; // default 'entities'
passage_nodes?: boolean; // default true
blend_vector?: boolean; // default true
};
// Query-time retrieval knobs (live; no re-ingest needed).
export type SourceRetrievalConfig = {
retriever?: string; // default 'classic' (only option for now)
@@ -40,6 +51,7 @@ export type SourceRetrievalConfig = {
score_threshold?: number | null; // default null
rephrase_query?: boolean; // default true
prescreen?: SourcePrescreenConfig | null; // null = off
graph?: SourceGraphRetrievalConfig; // graphrag retriever only
};
// Ingest-time GraphRAG extraction knobs (only used when kind === 'graphrag').
@@ -146,6 +146,11 @@ describe('round-trip configToOptions(optionsToConfig(x)) == x', () => {
batch_size: 5,
max_keep: 10,
},
graph: {
seed_strategy: 'relationships',
passage_nodes: false,
blend_vector: false,
},
},
graph: {
extraction_model: null,
@@ -157,6 +162,44 @@ describe('round-trip configToOptions(optionsToConfig(x)) == x', () => {
});
});
describe('graph retrieval options', () => {
it('defaults to the measured-best configuration', () => {
expect(DEFAULT_RETRIEVAL_OPTIONS.retrieval.graph).toEqual({
seed_strategy: 'entities',
passage_nodes: true,
blend_vector: true,
});
});
it('fills the defaults for a source saved before the options existed', () => {
const opts = configToOptions({ retrieval: { retriever: 'graphrag' } });
expect(opts.retrieval.graph).toEqual(
DEFAULT_RETRIEVAL_OPTIONS.retrieval.graph,
);
});
it('honors stored options and fills only the missing ones', () => {
const opts = configToOptions({
retrieval: { graph: { seed_strategy: 'relationships' } },
});
expect(opts.retrieval.graph).toEqual({
seed_strategy: 'relationships',
passage_nodes: true,
blend_vector: true,
});
});
it('writes the options into the retrieval block', () => {
const v = clone(DEFAULT_RETRIEVAL_OPTIONS);
v.retrieval.graph.blend_vector = false;
expect(optionsToConfig(v).retrieval?.graph).toEqual({
seed_strategy: 'entities',
passage_nodes: true,
blend_vector: false,
});
});
});
describe('isPrescreenConfigValid', () => {
const withPrescreen = (
chunks: number,
@@ -16,6 +16,7 @@ import {
import { Switch } from '../../components/ui/switch';
import type {
ChunkingStrategy,
GraphSeedStrategy,
RetrievalExposure,
SourceConfig,
} from '../../models/misc';
@@ -59,6 +60,11 @@ export type RetrievalOptionsValue = {
batch_size: number;
max_keep: number;
};
graph: {
seed_strategy: GraphSeedStrategy;
passage_nodes: boolean;
blend_vector: boolean;
};
};
graph: {
extraction_model: string | null;
@@ -85,6 +91,12 @@ export const DEFAULT_RETRIEVAL_OPTIONS: RetrievalOptionsValue = {
enabled: false,
...DEFAULT_PRESCREEN,
},
// The configuration that measured best across the corpora tested.
graph: {
seed_strategy: 'entities',
passage_nodes: true,
blend_vector: true,
},
},
graph: {
extraction_model: null,
@@ -204,6 +216,7 @@ export function configToOptions(config?: SourceConfig): RetrievalOptionsValue {
const chunking = config?.chunking ?? {};
const retrieval = config?.retrieval ?? {};
const prescreen = retrieval.prescreen ?? null;
const retrievalGraph = retrieval.graph ?? {};
const graph = config?.graph ?? {};
const d = DEFAULT_RETRIEVAL_OPTIONS;
return {
@@ -228,6 +241,14 @@ export function configToOptions(config?: SourceConfig): RetrievalOptionsValue {
batch_size: prescreen?.batch_size ?? DEFAULT_PRESCREEN.batch_size,
max_keep: prescreen?.max_keep ?? DEFAULT_PRESCREEN.max_keep,
},
graph: {
seed_strategy:
retrievalGraph.seed_strategy ?? d.retrieval.graph.seed_strategy,
passage_nodes:
retrievalGraph.passage_nodes ?? d.retrieval.graph.passage_nodes,
blend_vector:
retrievalGraph.blend_vector ?? d.retrieval.graph.blend_vector,
},
},
graph: {
extraction_model: graph.extraction_model ?? d.graph.extraction_model,
@@ -271,6 +292,11 @@ export function optionsToConfig(value: RetrievalOptionsValue): SourceConfig {
max_keep: ps.max_keep,
}
: null,
graph: {
seed_strategy: value.retrieval.graph.seed_strategy,
passage_nodes: value.retrieval.graph.passage_nodes,
blend_vector: value.retrieval.graph.blend_vector,
},
},
graph: {
extraction_model: value.graph.extraction_model?.trim()
@@ -399,6 +425,12 @@ export default function RetrievalOptions({
});
};
const setGraphRetrieval = (
patch: Partial<RetrievalOptionsValue['retrieval']['graph']>,
) => {
setRetrieval({ graph: { ...value.retrieval.graph, ...patch } });
};
const modelOptions = useMemo(() => {
const builtin: Model[] = [];
const user: Model[] = [];
@@ -611,6 +643,84 @@ export default function RetrievalOptions({
)}
</div>
{/* Graph retrieval group (graphrag only; live, so shown when testing too) */}
{isGraphRAG && (
<div className="flex flex-col gap-3">
<GroupHeader
title={tr('graphRetrieval.title')}
tag={tr('graphRetrieval.tag')}
/>
<p className="text-muted-foreground text-xs">
{tr('graphRetrieval.agentToolHint')}
</p>
<div className="divide-border/50 divide-y">
<SettingRow
label={tr('graphRetrieval.seedStrategy')}
htmlFor="graph-seed-strategy"
description={tr('graphRetrieval.seedStrategyHint')}
alignStart
>
<Select
value={value.retrieval.graph.seed_strategy}
disabled={disabled}
onValueChange={(v) =>
setGraphRetrieval({ seed_strategy: v as GraphSeedStrategy })
}
>
<SelectTrigger
id="graph-seed-strategy"
className="w-52 rounded-md"
size="lg"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="entities">
{tr('graphRetrieval.seedEntities')}
</SelectItem>
<SelectItem value="relationships">
{tr('graphRetrieval.seedRelationships')}
</SelectItem>
</SelectContent>
</Select>
</SettingRow>
<SettingRow
label={tr('graphRetrieval.passageNodes')}
htmlFor="graph-passage-nodes"
description={tr('graphRetrieval.passageNodesHint')}
alignStart
>
<Switch
id="graph-passage-nodes"
checked={value.retrieval.graph.passage_nodes}
disabled={disabled}
onCheckedChange={(checked) =>
setGraphRetrieval({ passage_nodes: checked })
}
/>
</SettingRow>
<SettingRow
label={tr('graphRetrieval.blendVector')}
htmlFor="graph-blend-vector"
description={tr('graphRetrieval.blendVectorHint')}
alignStart
>
<Switch
id="graph-blend-vector"
checked={value.retrieval.graph.blend_vector}
disabled={disabled}
onCheckedChange={(checked) =>
setGraphRetrieval({ blend_vector: checked })
}
/>
</SettingRow>
</div>
</div>
)}
{/* Graph extraction group (graphrag only; re-ingest required to apply) */}
{isGraphRAG && !queryOnly && (
<div className="flex flex-col gap-3">