Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import { StatusError } from '../../lib/streams/errors/status_error';
*
* Resolution order (delegated to `getForFeature`):
* 1. Admin override in the `inference_settings` SO (set via Model Settings page)
* 2. `recommendedEndpoints` from the feature registration
* 3. Platform default connector
* 2. Kibana global default (`GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR`), or the
* strict-default short-circuit when `GEN_AI_SETTINGS_DEFAULT_AI_CONNECTOR_DEFAULT_ONLY=true`
* 3. `recommendedEndpoints` from the feature registration
* 4. Platform default connector
*
* @throws StatusError(503) if the searchInferenceEndpoints plugin is unavailable.
* @throws StatusError(400) if no connector resolves for the feature.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import type { InferenceConnector } from '@kbn/inference-common';
import type { ListStreamDetail } from '@kbn/streams-plugin/server/routes/internal/streams/crud/route';
import type { OnboardingResult, TaskResult } from '@kbn/streams-schema';
import {
Expand Down Expand Up @@ -32,8 +33,10 @@ import type { OnboardingConfig } from '../shared/types';
const IN_PROGRESS_STATUSES = new Set<TaskStatus>([TaskStatus.InProgress, TaskStatus.BeingCanceled]);

interface ConnectorState {
connectors: InferenceConnector[];
resolvedConnectorId: string | undefined;
loading: boolean;
error: Error | undefined;
}

interface KiGenerationContextValue {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { useQueriesApi, type PromoteResult } from '../../../../../hooks/sig_even
import { useInvalidatePromoteRelatedQueries } from '../../../../../hooks/sig_events/use_invalidate_promote_queries';
import { getFormattedError } from '../../../../../util/errors';
import { useKibana } from '../../../../../hooks/use_kibana';
import { useAIFeatures } from '../../../../../hooks/use_ai_features';
import { AssetImage } from '../../../../asset_image';
import { LoadingPanel } from '../../../../loading_panel';
import { KnowledgeIndicatorDetailsFlyout } from '../../../stream_detail_significant_events_view/knowledge_indicator_details_flyout';
Expand Down Expand Up @@ -107,11 +106,11 @@ export function KnowledgeIndicatorsTable() {
bulkOnboardQueriesOnly,
} = useKiGeneration();

const aiFeatures = useAIFeatures();
const allConnectors = aiFeatures?.genAiConnectors?.connectors ?? [];
const connectorError = aiFeatures?.genAiConnectors?.error;
const isConnectorCatalogUnavailable =
!allConnectors.length || !!aiFeatures?.genAiConnectors?.loading || !!connectorError;
const connectorError = featuresConnectors.error ?? queriesConnectors.error;
const hasAnyConnectors =
featuresConnectors.connectors.length > 0 || queriesConnectors.connectors.length > 0;
const isAnyLoading = featuresConnectors.loading || queriesConnectors.loading;
const isConnectorCatalogUnavailable = !hasAnyConnectors || isAnyLoading || !!connectorError;

const runAndClearPicker = useCallback(
async (action: (names: string[]) => Promise<string[]>) => {
Expand Down Expand Up @@ -220,7 +219,8 @@ export function KnowledgeIndicatorsTable() {
<EuiFlexItem grow={false}>
<GenerateSplitButton
config={onboardingConfig}
allConnectors={allConnectors}
featuresConnectors={featuresConnectors.connectors}
queriesConnectors={queriesConnectors.connectors}
connectorError={connectorError}
featuresResolvedConnectorId={featuresConnectors.resolvedConnectorId}
queriesResolvedConnectorId={queriesConnectors.resolvedConnectorId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ import type { MenuHelpers } from './context_menu_split_button';

interface GenerateSplitButtonProps {
config: OnboardingConfig;
allConnectors: InferenceConnector[];
/**
* Connectors scoped to the KI Features (Knowledge Indicator extraction)
* feature: recommended endpoints first, then the rest of the catalog.
*/
featuresConnectors: InferenceConnector[];
/**
* Connectors scoped to the KI Queries (Knowledge Indicator query
* generation) feature: recommended endpoints first, then the rest of the
* catalog.
*/
queriesConnectors: InferenceConnector[];
connectorError: Error | undefined;
featuresResolvedConnectorId: string | undefined;
queriesResolvedConnectorId: string | undefined;
Expand All @@ -38,7 +48,8 @@ interface GenerateSplitButtonProps {

export const GenerateSplitButton = ({
config,
allConnectors,
featuresConnectors,
queriesConnectors,
connectorError,
featuresResolvedConnectorId,
queriesResolvedConnectorId,
Expand All @@ -51,12 +62,12 @@ export const GenerateSplitButton = ({
isLoading,
}: GenerateSplitButtonProps) => {
const featuresConnector = useMemo(
() => allConnectors.find((c) => c.connectorId === config.connectors.features),
[allConnectors, config.connectors.features]
() => featuresConnectors.find((c) => c.connectorId === config.connectors.features),
[featuresConnectors, config.connectors.features]
);
const queriesConnector = useMemo(
() => allConnectors.find((c) => c.connectorId === config.connectors.queries),
[allConnectors, config.connectors.queries]
() => queriesConnectors.find((c) => c.connectorId === config.connectors.queries),
[queriesConnectors, config.connectors.queries]
);

const onSelectFeaturesConnector = useCallback(
Expand Down Expand Up @@ -109,7 +120,7 @@ export const GenerateSplitButton = ({
],
},
buildConnectorSelectionPanel({
connectors: allConnectors,
connectors: featuresConnectors,
resolvedConnectorId: featuresResolvedConnectorId,
selectedConnectorId: config.connectors.features,
onSelect: (connectorId) => {
Expand All @@ -118,7 +129,7 @@ export const GenerateSplitButton = ({
},
}),
buildConnectorSelectionPanel({
connectors: allConnectors,
connectors: queriesConnectors,
resolvedConnectorId: queriesResolvedConnectorId,
selectedConnectorId: config.connectors.queries,
onSelect: (connectorId) => {
Expand All @@ -131,7 +142,8 @@ export const GenerateSplitButton = ({
isRunDisabled,
featuresConnector,
queriesConnector,
allConnectors,
featuresConnectors,
queriesConnectors,
featuresResolvedConnectorId,
queriesResolvedConnectorId,
config.connectors.features,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import { ContextMenuSplitButton } from '../shared/context_menu_split_button';
import type { MenuHelpers } from '../shared/context_menu_split_button';

interface InsightsSplitButtonProps {
allConnectors: InferenceConnector[];
/**
* Connectors scoped to the Discovery feature: recommended endpoints first,
* then the rest of the catalog. Drives the dropdown options.
*/
discoveryConnectors: InferenceConnector[];
connectorError: Error | undefined;
resolvedConnectorId: string | undefined;
displayConnectorId: string | undefined;
Expand All @@ -31,7 +35,7 @@ interface InsightsSplitButtonProps {
}

export const InsightsSplitButton = ({
allConnectors,
discoveryConnectors,
connectorError,
resolvedConnectorId,
displayConnectorId,
Expand All @@ -41,8 +45,8 @@ export const InsightsSplitButton = ({
isDisabled,
}: InsightsSplitButtonProps) => {
const discoveryConnector = useMemo(
() => allConnectors.find((c) => c.connectorId === displayConnectorId),
[allConnectors, displayConnectorId]
() => discoveryConnectors.find((c) => c.connectorId === displayConnectorId),
[discoveryConnectors, displayConnectorId]
);

const buildPanels = useCallback(
Expand All @@ -51,7 +55,7 @@ export const InsightsSplitButton = ({
items: [buildConnectorMenuItem({ connector: discoveryConnector, panelId: 1 })],
},
buildConnectorSelectionPanel({
connectors: allConnectors,
connectors: discoveryConnectors,
resolvedConnectorId,
selectedConnectorId: displayConnectorId,
onSelect: (connectorId) => {
Expand All @@ -60,7 +64,13 @@ export const InsightsSplitButton = ({
},
}),
],
[discoveryConnector, allConnectors, resolvedConnectorId, displayConnectorId, onConnectorChange]
[
discoveryConnector,
discoveryConnectors,
resolvedConnectorId,
displayConnectorId,
onConnectorChange,
]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import React, { useCallback, useEffect, useState } from 'react';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import type { TableRow } from './utils';
import { useInferenceFeatureConnectors } from '../../../../../hooks/sig_events/use_inference_feature_connectors';
import { useAIFeatures } from '../../../../../hooks/use_ai_features';
import { useKibana } from '../../../../../hooks/use_kibana';
import { useInsightsDiscoveryApi } from '../../../../../hooks/sig_events/use_insights_discovery_api';
import { useStreamsAppRouter } from '../../../../../hooks/use_streams_app_router';
Expand Down Expand Up @@ -74,11 +73,16 @@ export function StreamsView() {
const discoveryConnectors = useInferenceFeatureConnectors(
STREAMS_SIG_EVENTS_DISCOVERY_INFERENCE_FEATURE_ID
);
const aiFeatures = useAIFeatures();
const allConnectors = aiFeatures?.genAiConnectors?.connectors ?? [];
const connectorError = aiFeatures?.genAiConnectors?.error;
const isConnectorCatalogUnavailable =
!allConnectors.length || !!aiFeatures?.genAiConnectors?.loading || !!connectorError;

const connectorError =
featuresConnectors.error ?? queriesConnectors.error ?? discoveryConnectors.error;
const hasAnyConnectors =
featuresConnectors.connectors.length > 0 ||
queriesConnectors.connectors.length > 0 ||
discoveryConnectors.connectors.length > 0;
const isAnyLoading =
featuresConnectors.loading || queriesConnectors.loading || discoveryConnectors.loading;
const isConnectorCatalogUnavailable = !hasAnyConnectors || isAnyLoading || !!connectorError;

const [discoveryConnectorOverride, setDiscoveryConnectorOverride] = useState<
string | undefined
Expand Down Expand Up @@ -233,7 +237,8 @@ export function StreamsView() {
<EuiFlexItem grow={false}>
<GenerateSplitButton
config={onboardingConfig}
allConnectors={allConnectors}
featuresConnectors={featuresConnectors.connectors}
queriesConnectors={queriesConnectors.connectors}
connectorError={connectorError}
featuresResolvedConnectorId={featuresConnectors.resolvedConnectorId}
queriesResolvedConnectorId={queriesConnectors.resolvedConnectorId}
Expand All @@ -254,7 +259,7 @@ export function StreamsView() {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<InsightsSplitButton
allConnectors={allConnectors}
discoveryConnectors={discoveryConnectors.connectors}
connectorError={connectorError}
resolvedConnectorId={discoveryConnectors.resolvedConnectorId}
displayConnectorId={displayDiscoveryConnectorId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,37 @@
* 2.0.
*/

import { useMemo } from 'react';
import type { InferenceConnector } from '@kbn/inference-common';
import { useLoadConnectors } from '@kbn/inference-connectors';
import { useKibana } from '../use_kibana';
import { toInferenceConnector } from '../to_inference_connector';

const EMPTY_CONNECTORS: InferenceConnector[] = [];

export interface UseInferenceFeatureConnectorsResult {
/**
* Connector list for the inference feature, returned verbatim in the order
* the backend delivered it. This is what per-step model dropdowns render.
*/
connectors: InferenceConnector[];
/**
* Connector to pre-select and badge as "Default" — the first entry of the
* backend-supplied list.
*/
resolvedConnectorId: string | undefined;
loading: boolean;
error: Error | undefined;
}

/**
* Resolves the connector to use for a given inference feature.
* Delegates to useLoadConnectors from @kbn/inference-connectors
* and picks the best connector based on SO overrides vs recommended.
* Loads the connector list for a Significant Events inference feature.
*
* Delegates fetching, caching and error handling to the inference plugin's
* {@link useLoadConnectors}. The backend route is the single source of truth
* for ordering (admin SO override > global default > feature recommendeds >
* rest of catalog), so we render its response verbatim and pick the first
* entry as the pre-selected model.
*/
export function useInferenceFeatureConnectors(
featureId: string
Expand All @@ -30,17 +48,16 @@ export function useInferenceFeatureConnectors(
} = useKibana();

const query = useLoadConnectors({ http, toasts, featureId });
const connectors = query.data ?? [];
const aiConnectors = query.data;

// When an SO entry exists the API puts the configured connector first.
// Otherwise the API prepends the global default before the recommended
// ones, so we skip it and pick the first recommended connector instead.
const picked = query.soEntryFound
? connectors[0]
: connectors.find((c) => c.isRecommended) ?? connectors[0];
const connectors = useMemo<InferenceConnector[]>(
() => (aiConnectors?.length ? aiConnectors.map(toInferenceConnector) : EMPTY_CONNECTORS),
[aiConnectors]
);

return {
resolvedConnectorId: picked?.id,
connectors,
resolvedConnectorId: aiConnectors?.[0]?.id,
loading: query.isLoading || query.isFetching,
error: query.error ?? undefined,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { InferenceConnector, InferenceConnectorType } from '@kbn/inference-common';
import type { AIConnector } from '@kbn/inference-connectors';

/**
* Adapts an {@link AIConnector} (returned by `@kbn/inference-connectors`'s
* `useLoadConnectors`) to the {@link InferenceConnector} shape consumed by
* downstream Streams components. Kept in one place so both the GenAI parent
* hook and the per-feature sig-events hook agree on the mapping.
*/
export const toInferenceConnector = (c: AIConnector): InferenceConnector => ({
connectorId: c.id,
name: c.name,
type: c.actionTypeId as InferenceConnectorType,
config: 'config' in c ? (c.config as Record<string, unknown>) : {},
capabilities: {},
isPreconfigured: c.isPreconfigured,
isInferenceEndpoint: false,
isEis: c.isEis,
isDeprecated: c.isDeprecated,
isMissingSecrets: c.isMissingSecrets,
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { useCallback, useMemo } from 'react';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import type { HttpSetup } from '@kbn/core-http-browser';
import type { SettingsStart } from '@kbn/core-ui-settings-browser';
import type { InferenceConnector, InferenceConnectorType } from '@kbn/inference-common';
import { useLoadConnectors, type AIConnector } from '@kbn/inference-connectors';
import type { InferenceConnector } from '@kbn/inference-common';
import { useLoadConnectors } from '@kbn/inference-connectors';
import { STREAMS_INFERENCE_PARENT_FEATURE_ID } from '@kbn/streams-schema';
import { toInferenceConnector } from './to_inference_connector';

const STREAMS_CONNECTOR_STORAGE_KEY = 'xpack.streamsApp.lastUsedConnector';
const OLD_STORAGE_KEY = 'xpack.observabilityAiAssistant.lastUsedConnector';
Expand All @@ -26,19 +27,6 @@ export interface UseGenAIConnectorsResult {
isConnectorSelectionRestricted: boolean;
}

const toInferenceConnector = (c: AIConnector): InferenceConnector => ({
connectorId: c.id,
name: c.name,
type: c.actionTypeId as InferenceConnectorType,
config: 'config' in c ? (c.config as Record<string, unknown>) : {},
capabilities: {},
isPreconfigured: c.isPreconfigured,
isInferenceEndpoint: false,
isEis: c.isEis,
isDeprecated: c.isDeprecated,
isMissingSecrets: c.isMissingSecrets,
});

export function useGenAIConnectors({
http,
settings,
Expand Down
Loading