@coveo/headless
    Preparing search index...

    Use Relevance Generative Answering (RGA)

    A Coveo Machine Learning (Coveo ML) Relevance Generative Answering (RGA) model generates answers to complex natural language user queries in a Coveo-powered search interface.

    The RGA model leverages generative AI technology to generate an answer based solely on the content that you specify, such as your enterprise content. The content resides in a secure index in your Coveo organization.

    This article guides you through the process of integrating Coveo RGA in a front-end app with the @coveo/headless library. It focuses on a React implementation, but the basics of using the library in Angular and vanilla JavaScript will be covered.

    If you’re looking to jump into code, a great place to start is with the following React quickstart.

    Example React quickstart
    import {
    buildGeneratedAnswer,
    buildInteractiveCitation,
    buildSearchEngine,
    type GeneratedAnswerCitation,
    type GeneratedAnswerState,
    getSampleSearchEngineConfiguration,
    type InteractiveCitation,
    loadQueryActions,
    loadSearchActions,
    loadSearchAnalyticsActions,
    type QueryActionCreators,
    type SearchActionCreators,
    type SearchAnalyticsActionCreators,
    type SearchEngine,
    type SearchEngineOptions,
    } from '@coveo/headless';
    import {useCallback, useEffect, useMemo, useRef, useState} from 'react';

    export const AnswerGenerator = () => {
    const [rgaState, setRgaState] = useState<GeneratedAnswerState | undefined>();
    const inputRef = useRef<HTMLInputElement>(null);

    const engine: SearchEngine = useMemo(() => {
    const searchEngineOptions: SearchEngineOptions = {
    configuration: {
    ...getSampleSearchEngineConfiguration(),
    },
    };

    return buildSearchEngine(searchEngineOptions);
    }, []);

    const {generatedAnswer, updateQuery, logSearchboxSubmit, executeSearch} =
    useMemo(() => {
    const {updateQuery}: QueryActionCreators = loadQueryActions(engine);
    const {
    logInterfaceLoad,
    logSearchboxSubmit,
    }: SearchAnalyticsActionCreators = loadSearchAnalyticsActions(engine);
    const {executeSearch}: SearchActionCreators = loadSearchActions(engine);
    const generatedAnswer = buildGeneratedAnswer(engine);

    engine.dispatch(executeSearch(logInterfaceLoad()));

    return {
    generatedAnswer,
    updateQuery,
    logSearchboxSubmit,
    executeSearch,
    };
    }, [engine]);

    const submit = useCallback(() => {
    engine.dispatch(updateQuery({q: inputRef.current?.value}));
    engine.dispatch(executeSearch(logSearchboxSubmit()));
    }, [engine, updateQuery, logSearchboxSubmit, executeSearch, inputRef]);

    useEffect(() => {
    const unsubscribe = generatedAnswer.subscribe(() =>
    setRgaState(generatedAnswer.state)
    );
    return unsubscribe;
    }, [generatedAnswer, setRgaState]);

    return (
    <>
    {rgaState?.isLoading && <div>Loading...</div>}
    <input
    type="text"
    defaultValue={''}
    id="searchInput"
    ref={inputRef}
    disabled={rgaState?.isLoading}
    />
    <button type="button" onClick={submit}>
    Submit
    </button>

    {!rgaState?.isLoading && Boolean(rgaState?.answer) && (
    <div>
    <span className={`${rgaState?.isStreaming && 'rga-typing'} rga-text`}>
    {rgaState?.answer}
    </span>
    {!rgaState?.isStreaming && (
    <CitationsList
    citations={rgaState?.citations}
    searchEngine={engine}
    isStreaming={Boolean(rgaState?.isStreaming)}
    />
    )}
    </div>
    )}
    </>
    );
    };

    type TCitationsList = {
    citations?: GeneratedAnswerState['citations'];
    isStreaming: GeneratedAnswerState['isStreaming'];
    searchEngine: SearchEngine;
    };

    const CitationsList = ({
    citations,
    isStreaming,
    searchEngine,
    }: TCitationsList) => {
    if (!citations || isStreaming) return;

    return (
    <div>
    {citations.map((citation: GeneratedAnswerCitation) => (
    <Citation
    key={citation.id}
    citation={citation}
    searchEngine={searchEngine}
    />
    ))}
    </div>
    );
    };

    type TCitation = {
    citation: GeneratedAnswerCitation;
    searchEngine: SearchEngine;
    };

    const Citation = ({citation, searchEngine}: TCitation) => {
    const interactiveCitation: InteractiveCitation = buildInteractiveCitation(
    searchEngine,
    {
    options: {
    citation,
    },
    }
    );

    return (
    <button
    type="button"
    onClick={() => interactiveCitation.select()}
    onTouchEnd={() => interactiveCitation.cancelPendingSelect()}
    onTouchStart={() => interactiveCitation.beginDelayedSelect()}
    >
    {citation.title}
    </button>
    );
    };
    1. Refer to Citations List for more additional details.
    Warning

    Currently, only SearchEngine and InsightEngine support RGA.

    This document will concentrate on implementing RGA with a SearchEngine, but the interactions with GeneratedAnswer would be the same if you were to use the InsightEngine. The primary difference between the two implementations are the actions dispatched to the engines.

    First, use buildSearchEngine to instantiate the SearchEngine that will be used to create your RGA controller.

    /* lib/getSearchEngine.ts */
    import {
    buildSearchEngine,
    getSampleSearchEngineConfiguration,
    type SearchEngine,
    type SearchEngineOptions,
    } from '@coveo/headless';

    export const getSearchEngine = (): SearchEngine => {
    const searchEngineOptions: SearchEngineOptions = {
    configuration: {
    ...getSampleSearchEngineConfiguration(),
    search: {
    pipeline: 'genqatest',
    },
    },
    };

    return buildSearchEngine(searchEngineOptions);
    };
    1. Refer to Instantiating the engine and the controller to better understand your searchEngine controller. The above example is not intended for production use.
    Tip

    Type definitions

    The RGA controller generates responses based on the context of the query submitted to the engine. Create the RGA controller by passing a reference to an engine into buildGeneratedAnswer.

    You can optionally provide buildGeneratedAnswer with additional configuration options, as defined by GeneratedAnswerProps, to enhance the relevance of the response generated.

    The engine is what receives dispatched actions, not the RGA controller (GeneratedAnswer). This means that any code that uses RGA to generate a response needs a reference to the engine used to create the GeneratedAnswer controller.

    /* lib/getAnswerGenerator.ts */
    import {
    buildGeneratedAnswer,
    type GeneratedAnswer,
    type GeneratedAnswerProps,
    type SearchEngine,
    } from '@coveo/headless';

    export const getAnswerGenerator = (
    engine: SearchEngine,
    props?: GeneratedAnswerProps
    ): GeneratedAnswer => {
    return buildGeneratedAnswer(engine, props);
    };

    In the following example a SearchEngine is created. The GeneratedAnswer controller is created with that engine.

    /* lib/engines.ts */
    import type {
    GeneratedAnswer,
    GeneratedAnswerProps,
    SearchEngine,
    } from '@coveo/headless';
    import {getAnswerGenerator} from './getAnswerGenerator.js';
    import {getSearchEngine} from './getSearchEngine.js';

    export const headlessEngine: SearchEngine = getSearchEngine();

    export const answerGenerator = (
    props?: GeneratedAnswerProps
    ): GeneratedAnswer => {
    return getAnswerGenerator(headlessEngine, props);
    };
    Note

    All the underlying functionality of the engine remains available. The GeneratedAnswer controller is attached to the same engine, so search results are still accessible through engine.state.search while the GeneratedAnswer controller exposes RGA-specific state.

    Import the resulting engine into your component and dispatch actions to it. The @coveo/headless exports functions to create a common set of action creators, you can extend these with your own custom set. You’ll need the corresponding answerGenerator and to subscribe its state changes to access the RGA response in your component.

    /* components/AnswerGenerator.tsx */

    import {
    type GeneratedAnswerState,
    loadQueryActions,
    loadSearchActions,
    loadSearchAnalyticsActions,
    type QueryActionCreators,
    type SearchActionCreators,
    type SearchAnalyticsActionCreators,
    } from '@coveo/headless';
    import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
    import {answerGenerator, headlessEngine} from '../lib/engines.js';

    export const AnswerGenerator = () => {
    const [rgaState, setRgaState] = useState<GeneratedAnswerState>();
    const inputRef = useRef<HTMLInputElement>(null);

    const {updateQuery, executeSearch, logSearchboxSubmit, rgaController} =
    useMemo(() => {
    const rgaController = answerGenerator();
    const {updateQuery}: QueryActionCreators =
    loadQueryActions(headlessEngine);
    const {executeSearch}: SearchActionCreators =
    loadSearchActions(headlessEngine);
    const {logSearchboxSubmit}: SearchAnalyticsActionCreators =
    loadSearchAnalyticsActions(headlessEngine);
    return {
    rgaController,
    updateQuery,
    executeSearch,
    logSearchboxSubmit,
    };
    }, [headlessEngine]);

    const submitQuestion = useCallback(() => {
    headlessEngine.dispatch(updateQuery({q: inputRef.current?.value}));
    headlessEngine.dispatch(executeSearch(logSearchboxSubmit()));
    }, [updateQuery, executeSearch, logSearchboxSubmit]);

    useEffect(() => {
    const unsubscribe = rgaController.subscribe(() =>
    setRgaState(rgaController.state)
    );
    return unsubscribe;
    }, [setRgaState, rgaController]);

    return (
    <>
    <input
    type="text"
    defaultValue={''}
    id="searchInput"
    ref={inputRef}
    disabled={rgaState?.isLoading}
    />
    <button type="button" onClick={submitQuestion}>
    Submit
    </button>
    {rgaState?.isLoading && <div>Loading...</div>}
    <div>{rgaState?.answer}</div>
    </>
    );
    };
    1. Create a state variable to store the response from the RGA instance.
    2. Refer to loadQueryActions for additional details.
    3. Refer to loadSearchActions for additional details.
    4. Refer to loadSearchAnalyticsActions for additional details.
    5. Only instantiate new action creators if the engine has changed.
    6. Dispatch the action to submit your request for processing.
    7. Create an action to submit your question.
    8. Unsubscribe from the controller’s state updates when the component unmounts to prevent memory leaks and duplicate subscriptions.
    9. Subscribe to state updates from the GeneratedAnswer controller when the component mounts.

    The GeneratedAnswer controller provides methods to update its state and track user interactions. Following is a summary of the available methods, describing their effects on GeneratedAnswerState and whether they trigger analytics:

    Method Effect on state Analytics
    openFeedbackModal() Sets feedbackModalOpen to true.
    closeFeedbackModal() Sets feedbackModalOpen to false.
    like() Marks the generated answer as liked. ✅ Logs like event
    dislike() Marks the generated answer as disliked. ✅ Logs dislike event
    sendFeedback(feedback: GeneratedAnswerFeedback) Submits user feedback and sets feedbackSubmitted to true. ✅ Logs feedback event
    show() Marks the answer as visible. ✅ Logs show event
    hide() Marks the answer as hidden. ✅ Logs hide event
    expand() Marks the answer as expanded to show full response. ✅ Logs expand event
    collapse() Marks the answer as collapsed to show partial response. ✅ Logs collapse event
    enable() Enables the generated answer feature.
    disable() Disables the generated answer feature.
    logCitationClick(citationId: string) ✅ Logs citation click event
    logCitationHover(citationId: string, timeMs: number) ✅Logs citation hover event
    logCopyToClipboard() ✅ Logs copy-to-clipboard event
    retry() Tries to generate an answer again.

    You’ll need to integrate these events into your code to be able to generate reports for RGA in your Coveo Dashboard. You can also find details on what actions are automatically tracked by reviewing Relevance Generative Answering (RGA) reports and UA events.

    Tip

    Type definitions

    Along with the generated answer, RGA also returns the sources it used to produce that answer. These sources, called citations, are available through the citations field of the GeneratedAnswerState.

    Each citation follows the GeneratedAnswerCitation interface, which provides useful metadata such as the source title and URI. Citations allow users to access the original sources that contributed to the generated answer for additional context.

    The following example shows how to render a list of citations after the answer has finished streaming.

    /* components/CitationsList.tsx */
    import type {
    GeneratedAnswerCitation,
    GeneratedAnswerState,
    SearchEngine,
    } from '@coveo/headless';
    import {Citation} from './Citation.js';

    type TCitationsList = {
    citations?: GeneratedAnswerState['citations'];
    isStreaming: GeneratedAnswerState['isStreaming'];
    searchEngine: SearchEngine;
    };

    export const CitationsList = ({
    citations,
    isStreaming,
    searchEngine,
    }: TCitationsList) => {
    if (!citations || isStreaming) return;

    return (
    <div>
    {citations.map((citation: GeneratedAnswerCitation) => (
    <Citation
    key={citation.id}
    citation={citation}
    searchEngine={searchEngine}
    />
    ))}
    </div>
    );
    };
    1. These GeneratedAnswerCitation entities come from GeneratedAnswerState
    2. isStreaming is part of GeneratedAnswerState
    3. Citations should only be rendered once the full generated answer has completed streaming.

    Citations also support analytic events, refer to Citation Event Tracking for details.

    Use buildInteractiveCitation to track events on a specific citation. This allows for the gathering of analytics on which sources of information your users are finding valuable when interacting with generated answers.

    Below is a quick reference to the analytic tracking events provided by buildInteractiveCitation.

    Method Analytics
    beginDelayedSelect() ✅ Prepares to log selection event
    cancelPendingSelect() ✅ Cancels the pending selection of beginDelayedSelect
    select() ✅ Logs the selection event

    In order to track user interactions with results, you need to instantiate the InteractiveCitation with the specific GeneratedAnswerCitation being interacted with. Once these events have been integrated with your code, they can be accessed in your Coveo RGA reports.

    Following is an example of rendering a GeneratedAnswerCitation that implements event tracking.

    /* components/Citation.tsx */
    import {
    buildInteractiveCitation,
    type GeneratedAnswerCitation,
    type InteractiveCitation,
    type SearchEngine,
    } from '@coveo/headless';

    type TCitation = {
    citation: GeneratedAnswerCitation;
    searchEngine: SearchEngine;
    };

    export const Citation = ({citation, searchEngine}: TCitation) => {
    const interactiveCitation: InteractiveCitation = buildInteractiveCitation(
    searchEngine,
    {
    options: {
    citation,
    },
    }
    );

    return (
    <button
    type="button"
    onClick={() => interactiveCitation.select()}
    onTouchEnd={() => interactiveCitation.cancelPendingSelect()}
    onTouchStart={() => interactiveCitation.beginDelayedSelect()}
    >
    {citation.title}
    </button>
    );
    };
    1. Use buildInteractiveCitation to create a set of analytic actions that you can attach to an element containing a citation.

    The following is a basic implementation of the @coveo/headless RGA controller in an Angular component.

    Example Angular quickstart
    /* src/app/coveo-headless-rga.service.ts */
    import {Injectable} from '@angular/core';
    import {
    buildGeneratedAnswer,
    buildSearchBox,
    buildSearchEngine,
    type GeneratedAnswer,
    getSampleSearchEngineConfiguration,
    type SearchBox,
    type SearchEngine,
    } from '@coveo/headless';

    @Injectable({providedIn: 'root'})
    export class CoveoHeadlessRGAService {
    private _engine?: SearchEngine;

    initEngine() {
    if (this._engine) return this._engine;

    this._engine = buildSearchEngine({
    configuration: {
    ...getSampleSearchEngineConfiguration(),
    search: {
    pipeline: 'genqatest',
    },
    },
    });
    return this._engine;
    }

    get engine(): SearchEngine {
    if (!this._engine) {
    throw new Error(
    'Headless Engine not initialized. Call initEngine() first.'
    );
    }
    return this._engine;
    }

    buildSearchBox(): SearchBox {
    return buildSearchBox(this.engine);
    }

    buildGeneratedAnswer(): GeneratedAnswer {
    return buildGeneratedAnswer(this.engine);
    }
    }
    1. Refer to Instantiating the engine and the controller to better understand your searchEngine controller. The above example is not intended for production use.
    /*  src/app/coveo-headless-rga.component.ts */

    import {CommonModule} from '@angular/common';
    import {
    Component,
    inject,
    type OnDestroy,
    type OnInit,
    signal,
    } from '@angular/core';
    import {FormsModule} from '@angular/forms';
    import {
    type GeneratedAnswerState,
    loadQueryActions,
    loadSearchActions,
    loadSearchAnalyticsActions,
    type SearchBoxState,
    } from '@coveo/headless';
    import type {CoveoHeadlessRGAService as CoveoHeadlessRGAServiceType} from './coveo-headless-rga.service';
    import {CoveoHeadlessRGAService} from './coveo-headless-rga.service';

    @Component({
    selector: 'app-coveo-search',
    standalone: true,
    imports: [CommonModule, FormsModule],
    templateUrl: './coveo-headless-rga.component.html',
    })
    export class CoveoSearchComponent implements OnInit, OnDestroy {
    private searchBox!: ReturnType<CoveoHeadlessRGAServiceType['buildSearchBox']>;
    private generatedAnswer!: ReturnType<
    CoveoHeadlessRGAServiceType['buildGeneratedAnswer']
    >;

    searchBoxState = signal<SearchBoxState>({
    isLoading: false,
    isLoadingSuggestions: false,
    searchBoxId: '',
    suggestions: [],
    value: '',
    });
    generatedAnswerState = signal<GeneratedAnswerState | undefined>(undefined);

    private unsubscribers: Array<() => void> = [];
    private headless = inject(CoveoHeadlessRGAService);

    ngOnInit(): void {
    this.headless.initEngine();
    this.searchBox = this.headless.buildSearchBox();
    this.generatedAnswer = this.headless.buildGeneratedAnswer();

    const {logInterfaceLoad} = loadSearchAnalyticsActions(this.headless.engine);
    const {executeSearch} = loadSearchActions(this.headless.engine);
    this.headless.engine.dispatch(executeSearch(logInterfaceLoad()));

    this.unsubscribers.push(
    this.searchBox.subscribe(() =>
    this.searchBoxState.set(this.searchBox.state)
    ),
    this.generatedAnswer.subscribe(() => {
    return this.generatedAnswerState.set(this.generatedAnswer.state);
    })
    );
    }

    ngOnDestroy(): void {
    this.unsubscribers.forEach((u) => u());
    }

    onSearchSubmit(): void {
    const {updateQuery} = loadQueryActions(this.headless.engine);
    const {logSearchboxSubmit} = loadSearchAnalyticsActions(
    this.headless.engine
    );
    const {executeSearch} = loadSearchActions(this.headless.engine);
    this.headless.engine.dispatch(
    updateQuery({q: this.searchBoxState()?.value})
    );
    this.headless.engine.dispatch(executeSearch(logSearchboxSubmit()));
    }

    onClear(): void {
    const {updateQuery} = loadQueryActions(this.headless.engine);
    const {logSearchboxSubmit} = loadSearchAnalyticsActions(
    this.headless.engine
    );
    const {executeSearch} = loadSearchActions(this.headless.engine);

    this.headless.engine.dispatch(updateQuery({q: ''}));
    this.headless.engine.dispatch(executeSearch(logSearchboxSubmit()));
    }
    }
    <!-- src/app/coveo-headless-rga.component.ts -->
    <div class="coveo">
    <form class="search-row" (ngSubmit)="onSearchSubmit()">
    <input
    type="search"
    [(ngModel)]="searchBoxState().value"
    name="q"
    placeholder="Ask your question..."
    autocomplete="off"
    />
    <button type="submit">Search</button>
    <button type="button" class="ghost" (click)="onClear()">Clear</button>
    </form>

    <div class="layout">
    <main class="results">
    <div class="empty" *ngIf="generatedAnswerState()?.isLoading === false">
    {{generatedAnswerState()?.answer}}
    </div>
    </main>
    </div>
    </div>

    The following is a basic implementation of the @coveo/headless RGA Controller outside of any JavaScript framework.

    Example JavaScript quickstart
    import {
    buildGeneratedAnswer,
    buildSearchEngine,
    getSampleSearchEngineConfiguration,
    loadQueryActions,
    loadSearchActions,
    loadSearchAnalyticsActions,
    } from 'https://static.cloud.coveo.com/headless/v3/headless.esm.js';

    const coveoHeadlessRga = () => {
    const SUBMIT_BUTTON_ID = 'fetch-answer';
    const QUERY_INPUT_ID = 'query-input';
    const RESPONSE_DIV_ID = 'response';

    const searchEngine = buildSearchEngine({
    configuration: {
    ...getSampleSearchEngineConfiguration(),
    search: {
    pipeline: 'genqatest',
    },
    },
    });

    const rgaController = buildGeneratedAnswer(searchEngine);

    const {updateQuery} = loadQueryActions(searchEngine);
    const {executeSearch} = loadSearchActions(searchEngine);
    const {logInterfaceLoad, logSearchboxSubmit} =
    loadSearchAnalyticsActions(searchEngine);

    console.log('rgaController', rgaController);
    rgaController.subscribe(() => {
    const contentDiv = document.getElementById(RESPONSE_DIV_ID);
    console.log('rgaController', rgaController.state);
    if (!rgaController.state.answer) return;
    contentDiv.innerHTML = `
    <h2>Generated Answer Component</h2>
    <p>${rgaController.state.answer}</p>
    `;
    });

    document.getElementById(SUBMIT_BUTTON_ID).addEventListener('click', () => {
    const queryInput = document.getElementById(QUERY_INPUT_ID);
    const query = queryInput.value;
    if (!query) return;
    searchEngine.dispatch(updateQuery({q: query}));
    searchEngine.dispatch(executeSearch(logSearchboxSubmit()));
    });

    searchEngine.dispatch(executeSearch(logInterfaceLoad()));
    };

    document.addEventListener('DOMContentLoaded', coveoHeadlessRga);
    1. Refer to Instantiating the engine and the controller to better understand your searchEngine controller. The above example is not intended for production use.
    2. This function is executed every time that the state of rgaController is updated.
    3. The example configuration will only respond to the query 'how to resolve netflix connection with tivo'