Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
- Add agent summary ([#801](https://github.com/opensearch-project/dashboards-flow-framework/pull/801))
- Clean up agent summary formatting ([#803](https://github.com/opensearch-project/dashboards-flow-framework/pull/803))
- [Agentic Search] Add MCP server support ([#802](https://github.com/opensearch-project/dashboards-flow-framework/pull/802))
- [Agentic Search] Improve export / next steps UX ([#805](https://github.com/opensearch-project/dashboards-flow-framework/pull/805))
### Bug Fixes
### Infrastructure
### Documentation
Expand Down
38 changes: 38 additions & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@ export const AGENT_MAIN_DOCS_LINK =
'https://docs.opensearch.org/latest/ml-commons-plugin/agents-tools/agents/index/';
export const AGENTIC_SEARCH_DOCS_LINK =
'https://docs.opensearch.org/latest/vector-search/ai-search/agentic-search/';
export const AGENTIC_SEARCH_MODELS_DOCS_LINK =
'https://docs.opensearch.org/latest/vector-search/ai-search/agentic-search/agent-customization/#model-configuration';
export const AGENTIC_SEARCH_AGENTS_DOCS_LINK =
'https://docs.opensearch.org/latest/vector-search/ai-search/agentic-search/agent-customization/';
export const AGENTIC_SEARCH_MCP_DOCS_LINK =
'https://docs.opensearch.org/latest/vector-search/ai-search/agentic-search/mcp-server/';
export const MCP_CONNECTOR_DOCS_LINK =
'https://docs.opensearch.org/latest/ml-commons-plugin/agents-tools/mcp/mcp-connector';
export const MCP_AGENT_CONFIG_DOCS_LINK =
Expand Down Expand Up @@ -1033,6 +1039,10 @@ export enum COMPONENT_ID {
// not override the default styles from the EuiCard component.
export const LEFT_NAV_SELECTED_STYLE = '2px solid rgba(128, 128, 128, 0.8)';

/**
* Agents / tools constants
*/

// Derived from https://docs.opensearch.org/latest/ml-commons-plugin/agents-tools/agents/index/
export enum AGENT_TYPE {
FLOW = 'flow',
Expand Down Expand Up @@ -1104,3 +1114,31 @@ export const DEFAULT_MCP_SERVER = {
mcp_connector_id: '',
tool_filters: [],
} as MCPConnector;

export const EXAMPLE_PUT_AGENTIC_SEARCH_PIPELINE = `PUT _search/pipeline/agentic-pipeline
{
"request_processors": [
{
"agentic_query_translator": {
"agent_id": "${AGENT_ID_PATTERN}"
}
}
],
"response_processors": [
{
"agentic_context": {
"agent_steps_summary": true,
"dsl_query": true
}
}
]
}`;

export const EXAMPLE_AGENTIC_SEARCH_QUERY = `GET <your-index>/_search?search_pipeline=agentic-pipeline
{
"query": {
"agentic": {
"query_text": "<your-search-query>",
}
}
}`;
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import {
import {
Agent,
AGENT_ID_PATH,
AGENT_MAIN_DOCS_LINK,
AGENT_TYPE,
AGENTIC_SEARCH_AGENTS_DOCS_LINK,
AGENTIC_SEARCH_MCP_DOCS_LINK,
customStringify,
DEFAULT_AGENT,
EMPTY_AGENT,
MAX_DESCRIPTION_LENGTH,
MAX_STRING_LENGTH,
MCP_CONNECTOR_DOCS_LINK,
NEW_AGENT_ID_PLACEHOLDER,
NEW_AGENT_PLACEHOLDER,
WorkflowConfig,
Expand Down Expand Up @@ -233,7 +233,10 @@ export function AgentConfiguration(props: AgentConfigurationProps) {
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiText size="s" color="subdued">
<EuiLink target="_blank" href={AGENT_MAIN_DOCS_LINK}>
<EuiLink
target="_blank"
href={AGENTIC_SEARCH_AGENTS_DOCS_LINK}
>
Documentation
</EuiLink>
</EuiText>
Expand Down Expand Up @@ -434,7 +437,7 @@ export function AgentConfiguration(props: AgentConfigurationProps) {
labelAppend={
<EuiText size="xs">
<EuiLink
href={MCP_CONNECTOR_DOCS_LINK}
href={AGENTIC_SEARCH_MCP_DOCS_LINK}
target="_blank"
>
Learn more
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EuiFormRow, EuiLink, EuiSelect, EuiText } from '@elastic/eui';
import {
Agent,
AGENT_TYPE,
AGENTIC_SEARCH_DOCS_LINK,
AGENTIC_SEARCH_MODELS_DOCS_LINK,
AgentLLM,
Model,
MODEL_STATE,
Expand Down Expand Up @@ -76,7 +76,7 @@ export function AgentLLMFields({
fullWidth
labelAppend={
<EuiText size="xs">
<EuiLink href={AGENTIC_SEARCH_DOCS_LINK} target="_blank">
<EuiLink href={AGENTIC_SEARCH_MODELS_DOCS_LINK} target="_blank">
Learn more
</EuiLink>
</EuiText>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { AgenticSearchApplicationContent } from './agentic_search_application_content';
import { Workflow } from '../../../../common';

const TEST_AGENT_ID = 'test-agent-id';
const TEST_WORKFLOW = {
ui_metadata: {
config: {
search: {
requestAgentId: {
value: TEST_AGENT_ID,
},
},
},
},
} as Workflow;
const TEST_WORKFLOW_NO_AGENT = {
ui_metadata: {
config: {
search: {
requestAgentId: {
value: '',
},
},
},
},
} as Workflow;

describe('AgenticSearchApplicationContent', () => {
test('renders the component with agent', () => {
render(<AgenticSearchApplicationContent workflow={TEST_WORKFLOW} />);

expect(screen.queryByTestId('noAgentFoundCallout')).not.toBeInTheDocument();
expect(screen.getByTestId('searchPipelineCodeBlock')).toBeInTheDocument();
expect(screen.getByTestId('agenticSearchCodeBlock')).toBeInTheDocument();
});
test('renders the component with no agent', () => {
render(
<AgenticSearchApplicationContent workflow={TEST_WORKFLOW_NO_AGENT} />
);

expect(screen.getByTestId('noAgentFoundCallout')).toBeInTheDocument();
expect(
screen.queryByTestId('searchPipelineCodeBlock')
).not.toBeInTheDocument();
expect(
screen.queryByTestId('agenticSearchCodeBlock')
).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import {
EuiAccordion,
EuiCallOut,
EuiCodeBlock,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiText,
} from '@elastic/eui';
import {
AGENT_ID_PATTERN,
AGENTIC_SEARCH_DOCS_LINK,
EXAMPLE_AGENTIC_SEARCH_QUERY,
EXAMPLE_PUT_AGENTIC_SEARCH_PIPELINE,
Workflow,
} from '../../../../common';

interface AgenticSearchApplicationContentProps {
workflow?: Workflow;
}

const NO_AGENT_FOUND_TEXT =
'No agent found. Make sure to select an agent before trying to use agentic search in your application.';

/**
* Static content for next steps & how to use agentic search in your application
*/
export function AgenticSearchApplicationContent(
props: AgenticSearchApplicationContentProps
) {
const selectedAgentId = (props.workflow?.ui_metadata?.config?.search
?.requestAgentId?.value ?? '') as string;

return (
<EuiFlexGroup direction="column" gutterSize="s">
{!selectedAgentId ? (
<EuiFlexItem>
<EuiCallOut
size="s"
color="warning"
data-testid="noAgentFoundCallout"
>
{NO_AGENT_FOUND_TEXT}
</EuiCallOut>
</EuiFlexItem>
) : (
<>
<EuiFlexItem grow={false}>
<EuiText size="s">
To use agentic search in your application, create a search
pipeline and run <code>agentic</code>
{' '}
search queries. Use the following examples as a starting point.
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiAccordion
id="agenticSearchPipeline"
paddingSize="s"
buttonContent={
<EuiText size="s">1. Create a search pipeline</EuiText>
}
initialIsOpen={true}
>
<EuiCodeBlock
data-testid="searchPipelineCodeBlock"
language="json"
fontSize="s"
paddingSize="s"
overflowHeight={400}
whiteSpace="pre"
isCopyable={true}
>
{injectAgentId(
EXAMPLE_PUT_AGENTIC_SEARCH_PIPELINE,
selectedAgentId
)}
</EuiCodeBlock>
</EuiAccordion>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiAccordion
id="agenticSearchQuery"
paddingSize="s"
buttonContent={<EuiText size="s">2. Run a search query</EuiText>}
initialIsOpen={true}
>
<EuiCodeBlock
data-testid="agenticSearchCodeBlock"
language="json"
fontSize="s"
paddingSize="s"
overflowHeight={300}
whiteSpace="pre"
isCopyable={true}
>
{EXAMPLE_AGENTIC_SEARCH_QUERY}
</EuiCodeBlock>
</EuiAccordion>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
For more details and examples, check out the{' '}
<EuiLink target="_blank" href={AGENTIC_SEARCH_DOCS_LINK}>
full documentation
</EuiLink>
</EuiText>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
);
}

function injectAgentId(template: string, agentId: string) {
return template.replaceAll(AGENT_ID_PATTERN, agentId);
}
73 changes: 73 additions & 0 deletions public/pages/workflow_detail/components/export_modal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Workflow, WORKFLOW_TYPE } from '../../../../common';
import { ExportModal } from './export_modal';

const TEST_WORKFLOW_NAME = 'test-workflow-name';
const TEST_WORKFLOW_CUSTOM = {
name: TEST_WORKFLOW_NAME,
ui_metadata: {
type: WORKFLOW_TYPE.CUSTOM,
config: {},
},
} as Workflow;
const TEST_WORKFLOW_AGENTIC = {
name: TEST_WORKFLOW_NAME,
ui_metadata: {
type: WORKFLOW_TYPE.AGENTIC_SEARCH,
config: {
search: {
requestAgentId: {
value: 'test-agent-id',
},
},
},
},
} as Workflow;

jest.mock('../../../services', () => {
const { mockCoreServices } = require('../../../../test');
return {
...jest.requireActual('../../../services'),
...mockCoreServices,
};
});

describe('ExportTemplateContent', () => {
global.URL.createObjectURL = jest.fn();

test('renders the component for non-agentic-search usecases', () => {
render(
<ExportModal
workflow={TEST_WORKFLOW_CUSTOM}
setIsExportModalOpen={jest.fn()}
/>
);

expect(
screen.getByTestId('exportDataToggleButtonGroup')
).toBeInTheDocument();
expect(screen.queryByTestId('agenticSearchTabs')).not.toBeInTheDocument();
});

test('renders the component for agentic-search usecase', () => {
render(
<ExportModal
workflow={TEST_WORKFLOW_AGENTIC}
setIsExportModalOpen={jest.fn()}
/>
);
expect(
screen.queryByTestId('exportDataToggleButtonGroup')
).not.toBeInTheDocument();
expect(screen.getByTestId('agenticSearchTabs')).toBeInTheDocument();
expect(screen.getByTestId('searchPipelineCodeBlock')).toBeInTheDocument();
expect(screen.getByTestId('agenticSearchCodeBlock')).toBeInTheDocument();
});
});
Loading
Loading