Once you have set up your index, you will be able to engage in dynamic conversations with it.
The Orama Cloud SDK exposes a very convenient function for performing highly dynamic generative AI experiences with your data through the answerSession
API.
Let’s see what it looks like.
Getting Started
Follow the instructions here to install the official JavaScript SDK. After you’ve installed it, you will be able to create your first answer session:
import { OramaClient } from " @oramacloud/client " ;
const orama = new OramaClient ( {
endpoint: " YOUR_ENDPOINT_URL " ,
api_key: " YOUR_PUBLIC_API_KEY " ,
const answerSession = orama . createAnswerSession ( {
userContext: " The user is a very skilled programmer but has never used Orama before. " ,
inferenceType: " documentation " ,
onMessageChange : ( messages ) => console . log ( { messages } ) ,
onMessageLoading : ( loading ) => console . log ( { loading } ) ,
onAnswerAborted : ( aborted ) => console . log ( { aborted } ) ,
onSourceChange : ( sources ) => console . log ( { sources } ) ,
onQueryTranslated : ( query ) => console . log ( { query } ) ,
onStateChange : ( state ) => console . log ( { state } ) ,
onNewInteractionStarted : ( interactionId ) => console . log ( { interactionId } ) ,
await answerSession . ask ({
term: " How do I get started with Orama? " ,
let clientParams = OramaClientParams ( endpoint : " YOUR_ENDPOINT_URL " , apiKey : " YOUR_PUBLIC_API_KEY " )
let orama = OramaClient ( params : clientParams )
let answerParams = AnswerParams < E2EDoc > (
userContext : " The user is a very skilled programmer but has never used Orama before. " ,
inferenceType : . documentation ,
oramaClient : oramaClient,
answerSession = AnswerSession ( params : answerParams )
import com.orama.client.OramaClient
val client = OramaClient (
apiKey = "YOUR_PUBLIC_API_KEY" ,
endpoint = "YOUR_ENDPOINT_URL"
val answerParams = AnswerParams (
serializer = MyDoc. serializer (),
userContext = "The user is a very skilled programmer but has never used Orama before."
val answerSession = AnswerSession (answerParams)
All the parameters above are optional, but you may need to use them to create great answer sessions for your users. Let’s see them in detail.
userContext
userContext
is a string or an object that describes the user’s context. This is useful when you want to provide a more personalized experience to your users. For example, if you know that the user is a beginner in programming, you can provide a more detailed explanation of the concepts you’re talking about. This is optional, but it’s a good practice to provide it.
If we pretend to be an online music store, and we know who the logged in user is, we could provide the following user context:
const answerSession = orama . createAnswerSession ( {
userContext: ' The user is called John Doe and he loves rock music. He has been a customer for 5 years. '
let clientParams = OramaClientParams ( endpoint : "" , apiKey : "" )
let orama = OramaClient ( params : clientParams )
let answerParams = AnswerParams < E2EDoc > (
userContext : " The user is a very skilled programmer but has never used Orama before. " ,
inferenceType : . documentation ,
oramaClient : oramaClient,
let answerSession = AnswerSession ( params : answerParams )
import com.orama.client.OramaClient
val client = OramaClient (apiKey = "" , endpoint = "" )
val answerParams = AnswerParams (
serializer = MyDoc. serializer (),
userContext = "The user is a very skilled programmer but has never used Orama before."
val answerSession = AnswerSession (answerParams)
If we prefer, we can also use an object to provide a more structured context:
const answerSession = orama . createAnswerSession ( {
musicTaste: [ ' Rock ' , ' Punk ' , ' Metal ' ] ,
let clientParams = OramaClientParams ( endpoint : "" , apiKey : "" )
let orama = OramaClient ( params : clientParams )
let answerParams = AnswerParams < E2EDoc > (
userContext : " { name: \" John Doe \" , musicTaste: [ \" Rock \" , \" Punk \" , \" Metal \" ], customerSince: 2019 } " ,
inferenceType : . documentation ,
let answerSession = AnswerSession ( params : answerParams )
import com.orama.client.OramaClient
val client = OramaClient (apiKey = "" , endpoint = "" )
val answerParams = AnswerParams (
serializer = MyDoc. serializer (),
userContext = "{ name: \" John Doe \" , musicTaste: [ \" Rock \" , \" Punk \" , \" Metal \" ], customerSince: 2019 }"
val answerSession = AnswerSession (answerParams)
inferenceType
inferenceType
refers to the type of inference that runs on your data. Right now, only documentation
is supported - therefore it’s set as a default value - but we’ll enable more inference types soon (website
, blog
, ecommerce
, generic
, and more)
initialMessages
An array of initial messages for your conversational experience with Orama Cloud. By default, this parameter is an empty array, but it can be an array of objects in the following format:
const initialMessages = [
{ role: " user " , content: " What is Orama? " },
{ role: " assistant " , content: " Orama is a next-generation answer engine " },
AnswerParams < Doc > . Message ( role : . user , content : " What is Orama? " )
AnswerParams < Doc > . Message ( role : . assistant , content : " Orama is a next-generation answer engine " )
import com.orama.client.OramaClient
var initialMessages = listOf (
Message (role = Role.USER, content = "What is Orama?" ),
Message (role = Role.ASSISTANT, content = "Orama is a next-generation answer engine." ),
events
The events to subscribe to. You will get notified whenever a new event gets triggered. More on this in the next section.
Session Events
Answers are a highly dynamic part of Orama. They work in an asynchronous manner and requires you to subscribe to events to catch any new change in the answer flow and react to it.
At the time of writing, Orama supports the following events:
onStateChange
Runs everytime the state of the answer session changes. In this context, the state refers to a list of interactions between the user and Orama Cloud.
Each interaction looks like this:
import type { AnyDocument, AnyOrama, Nullable, Results, SearchParams } from " @oramacloud/client " ;
export type Interaction< T = AnyDocument > = {
relatedQueries : Nullable < string []>,
sources : Nullable < Results < T >>,
translatedQuery : Nullable < SearchParams < AnyOrama >>,
struct Interaction< T : Codable > {
var interactionId: String
var relatedQueries: [ String ] ?
var sources: SearchResults<T> ?
var translatedQuery: ClientSearchParams ?
var aborted: Bool = false
import com.orama.client.OramaClient
data class Interaction<T> (
val interactionId: String,
val relatedQueries: List<String>?,
val sources: List<Hit<T>>,
val translatedQuery: Map<String, JsonElement>,
val aborted: Boolean = false ,
Consequently, the onStateChange
event will return an array of these interactions:
const answerSession = orama . createAnswerSession ( {
onStateChange : ( state ) => {
if ( state . every ( interaction => ! interaction . loading )) {
await answerSession . ask ({
// interactionId: "clyru4rl8000008l062b26fk1",
// query: "What is Orama?",
// response: "Orama is a next-generation answer engine [...]",
// relatedQueries: ["How Orama works", "Why Orama is the best", "Vector search with orama"],
// elapsed: { formatted: "78ms", raw: 78000000 },
// { document: { title: "What is Orama", ... } },
// { document: { title: "How Orama works", ... } },
// { document: { title: "Why Orama is the best", ... } }
// translatedQuery: { term: "What is Orama?" },
let answerSession = AnswerSession ( params : answerParams )
. on ( event : . stateChange ) { state in
if state. allSatisfy ( { !$0 . loading } ) {
try await answerSession. ask ( params : AnswerParams. AskParams (
// interactionId: "clyru4rl8000008l062b26fk1",
// query: "What is Orama?",
// response: "Orama is a next-generation answer engine [...]",
// relatedQueries: ["How Orama works", "Why Orama is the best", "Vector search with orama"],
// elapsed: { formatted: "78ms", raw: 78000000 },
// { document: { title: "What is Orama", ... } },
// { document: { title: "How Orama works", ... } },
// { document: { title: "Why Orama is the best", ... } }
// translatedQuery: { term: "What is Orama?" },
val answerSession = AnswerSession (answerParams, events = object : AnswerEventListener<MyDoc> {
override fun onStateChange (state: MutableList<Interaction<MyDoc>>) {
// interactionId: "clyru4rl8000008l062b26fk1",
// query: "What is Orama?",
// response: "Orama is a next-generation answer engine [...]",
// relatedQueries: ["How Orama works", "Why Orama is the best", "Vector search with orama"],
// elapsed: { formatted: "78ms", raw: 78000000 },
// { document: { title: "What is Orama", ... } },
// { document: { title: "How Orama works", ... } },
// { document: { title: "Why Orama is the best", ... } }
// translatedQuery: { term: "What is Orama?" },
The onStateChange
event gets triggered everytime there’s a change in the state, which means that it will also get triggered when the server streams a new chunk of the answer back to the client.
This is particularly useful when creating reactive interfaces that need to update the UI as the answer gets streamed back from the server with frameworks like React, Vue, or Angular.
You can always access a static version of the state by using the state
getter:
const answerSession = orama . createAnswerSession ();
await answerSession . ask ({
console . log ( answerSession . state );
// interactionId: "clyru4rl8000008l062b26fk1",
// query: "What is Orama?",
// response: "Orama is a next-generation answer engine [...]",
// relatedQueries: ["How Orama works", "Why Orama is the best", "Vector search with orama"],
// elapsed: { formatted: "78ms", raw: 78000000 },
// { document: { title: "What is Orama", ... } },
// { document: { title: "How Orama works", ... } },
// { document: { title: "Why Orama is the best", ... } }
// translatedQuery: { term: "What is Orama?" },
let answerSession = AnswerSession ( params : answerParams )
try await answerSession. ask ( params : AnswerParams. AskParams (
print ( answerSession. getState ())
// interactionId: "clyru4rl8000008l062b26fk1",
// query: "What is Orama?",
// response: "Orama is a next-generation answer engine [...]",
// relatedQueries: ["How Orama works", "Why Orama is the best", "Vector search with orama"],
// elapsed: { formatted: "78ms", raw: 78000000 },
// { document: { title: "What is Orama", ... } },
// { document: { title: "How Orama works", ... } },
// { document: { title: "Why Orama is the best", ... } }
// translatedQuery: { term: "What is Orama?" },
val answerSession = AnswerSession (answerParams)
val answer = answerSession. ask ( AskParams (
query = "Query is Orama?"
println (answerSession. getState ())
// interactionId: "clyru4rl8000008l062b26fk1",
// query: "What is Orama?",
// response: "Orama is a next-generation answer engine [...]",
// relatedQueries: ["How Orama works", "Why Orama is the best", "Vector search with orama"],
// elapsed: { formatted: "78ms", raw: 78000000 },
// { document: { title: "What is Orama", ... } },
// { document: { title: "How Orama works", ... } },
// { document: { title: "Why Orama is the best", ... } }
// translatedQuery: { term: "What is Orama?" },
onMessageChange
Runs everytime a message changes. This gets triggered with each chunk that gets streamed from the server, allowing you to re-render the messages in your page as they gets updated.
const answerSession = orama . createAnswerSession ( {
onMessageChange : ( messages ) => {
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
await answerSession . ask ({
let answerSession = AnswerSession ( params : answerParams )
. on ( event : . messageChange ) { print ( $0 ) }
try await answerSession. ask ( params : AnswerParams. AskParams (
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
val answerSession = AnswerSession (answerParams, events = object : AnswerEventListener<MyDoc> {
override fun onMessageChange ( data : String) {
val answer = answerSession. ask ( AskParams (
query = "Query is Orama?"
onMessageLoading
Gets triggered everytime an answer request starts or ends.
const answerSession = orama . createAnswerSession ( {
onMessageLoading : ( loading ) => {
console . log ( " Still loading the messages... " ) ;
await answerSession . ask ({
let answerSession = AnswerSession ( params : answerParams )
. on ( event : . onMessageLoading ) {
print ( " Still loading the messages... " )
try await answerSession. ask ( params : AnswerParams. AskParams (
val answerSession = AnswerSession (answerParams, events = object : AnswerEventListener<MyDoc> {
override fun onMessageLoading (loading: Boolean) {
println ( "Still loading the messages..." )
val answer = answerSession. ask ( AskParams (
query = "Query is Orama?"
onAnswerAborted
Gets triggered when an answer gets aborted.
const answerSession = orama . createAnswerSession ( {
onAnswerAborted : ( aborted ) => {
alert ( " The user has aborted this answer generation! " ) ;
await answerSession . ask ({
answerSession . abortAnswer ();
val abortHandler = AbortHandler ()
val answerSession = AnswerSession (answerParams, events = object : AnswerEventListener<MyDoc> {
override fun onAnswerAborted (aborted: Boolean) {
println ( "The user has aborted this answer generation!" )
}, abortHandler = abortHandler)
val answer = answerSession. ask ( AskParams (
query = "Query is Orama?"
onSourceChange
Orama Cloud will return the results of the inference process as a standard set of Orama search results. You will have access to those as soon as they gets computed:
const answerSession = orama . createAnswerSession ( {
onSourceChange : ( sources ) => {
` Got ${ sources . count } sources in ${ sources . elapsed . formatted } . `
` Top three sources are: ${ sources . hits
. map ( ( hit ) => hit . document . title )
// Got 15 sources in 78ms.
// Top three sources are: What is Orama, How Orama works, Why Orama is the best
await answerSession . ask ({
let answerSession = AnswerSession ( params : answerParams )
. on ( event : . onSourceChange ) {
print ( " Got \( $0 .count ) sources in \( $0 . elapsed . formatted ) . " )
print ( " Top three sources are: \( $0 . hits . map { hit in hit. document . title }. joined ( separator : " , " ) ) " )
try await answerSession. ask ( params : AnswerParams. AskParams (
// Got 15 sources in 78ms.
// Top three sources are: What is Orama, How Orama works, Why Orama is the best
val answerSession = AnswerSession (answerParams, events = object : AnswerEventListener<MyDoc> {
override fun onSourceChanged (sources: List<Hit<MyDoc>>) {
println ( "Got ${sources.size}" )
val answer = answerSession. ask ( AskParams (
query = "Query is Orama?"
onQueryTranslated
Gets triggered when Orama successfully translates a natural language query into a search query. This is useful when you want to know how Orama interprets the user’s input. It adapts to your index schema and returns the most relevant search query.
const answerSession = orama . createAnswerSession ( {
onQueryTranslated : ( query ) => {
// { term: 'Halfpipe skateboard', where: { price: { lt: 100 } } }
await answerSession . ask ({
term: " What's the best skateboard for halfpipes under $100? " ,
let answerSession = AnswerSession ( params : answerParams )
. on ( event : . onQueryTranslated ) { print ( $0 ) }
try await answerSession. ask ( params : AnswerParams. AskParams (
query : " What's the best skateboard for halfpipes under $100? " ,
// { term: 'Halfpipe skateboard', where: { price: { lt: 100 } } }
val answerSession = AnswerSession (answerParams, events = object : AnswerEventListener<MyDoc> {
override fun onQueryTranslated (query: Map<String, JsonElement>) {
val answer = answerSession. ask ( AskParams (
query = "What's the best skateboard for halfpipes under $100 ?"
Asking a Question
With the Orama JavaScript SDK, you can ask questions in two different ways:
using the .ask
function
using the .askStream
function
Both methods accept the search parameters you already use to perform search, so you can influence the inference process by filtering, sorting, and grouping the results as you prefer!
They both trigger the exact same events, but the only difference is how they return the answer.
The .ask
function will return the entire answer once it has been entirely streamed from the server, therefore, you can use this method as follows:
const answer = await answerSession . ask ( {
// Orama is a next-generation answer engine
let answer = try await answerSession. ask ( params : AnswerParams. AskParams (
val answer = answerSession. ask ( AskParams (
The .askStream
method returns an async iterator that yields chunks of the answer as they gets streamed back from the server. Therefore, this is how you would implement it:
const answer = await answerSession . askStream ( {
for await ( const msg of answer ) {
let answer = try await answerSession. askStream ( params : AnswerParams. AskParams (
for try await msg in answer {
val answer = answerSession. askStream ( AskParams (
Aborting Sessions
In any moment in time (when a user cancels an answer request, for example) you can abort an answer by using the .abortAnswer
function:
answerSession . abortAnswer ();
val myAbortHandler = AbortHandler ()
val answerSession = AnswerSession (answerParams, abortHandler = myAbortHandler)
val answer = answerSession. ask ( AskParams (
query = "Query is Orama?"
This will trigger the onAnswerAborted
event, which will simply return a true
in its callback function:
const answerSession = orama . createAnswerSession ( {
onAnswerAborted : ( aborted ) => {
alert ( " The user aborted the answer session! " ) ;
val answerSession = AnswerSession (answerParams, events = object : AnswerEventListener<MyDoc> {
override fun onAnswerAborted (aborted: Boolean) {
println ( "The user has aborted this answer generation!" )
}, abortHandler = abortHandler)
When creating answer sessions, you may want to provide your users with a list of related queries that they can use to get more information about a specific topic. You can do this by using the related
parameter:
When asking for "question"
(default) related queries, Orama will return an array of related queries in a question format. For example, if the user asks for: “What is Orama?”, Orama will return an array of related queries like: ["How does Orama work?", "Why is Orama the best?", "How do I run vector search?"]
.
If the format is "query"
, Orama will return an array of related queries in a query format. For example, if the user asks for: “What is Orama?”, Orama will return an array of related queries like: ["How Orama works", "Why Orama is the best", "Vector search with Orama"]
.
const answerSession = orama . createAnswerSession ( {
onRelatedQueries : ( relatedQueries ) => {
console . log ( relatedQueries ) ;
// ["How Orama works", "Why Orama is the best", "Vector search with Orama"]
await answerSession . ask ({
howMany: 3 , // How many related queries you want to get. Maximum is 5.
let answer = try await answerSession. ask ( params : AnswerParams. AskParams (
userData : " The user is a beginner in programming " ,
related : AnswerParams < MyDoc > . RelatedQueries ( howMany : 3 , format : " query " )
Getting all the messages
At any point in time, you can retrieve all the messages by calling the .getMessages
function:
const messages = answerSession . getMessages ();
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
let messages = answerSession. getMessages ()
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
val messages = answerSession. getMessages ()
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
Regenerating the last answer
Sometimes, the user may want to regenerate the last answer. You can do this by calling the .regenerateLast
method:
await answerSession . ask ({ term: " What is Orama? " });
// "stream" is true by default, but you can set it to false if you want to get the entire answer at once
await answerSession . regenerateLast ({ stream: false });
const messages = answerSession . getMessages ();
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
let answer = try await answerSession. ask ( params : AnswerParams. AskParams (
// "stream" is true by default, but you can set it to false if you want to get the entire answer at once
let answer = try await answerSession. regenerateLast ( stream : false )
let messages = answerSession. getMessages ()
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
var answer = answerSession. ask ( AskParams (
answer = answerSession. regenerateLast ()
val messages = answerSession. getMessages ()
// { role: 'user', content: 'What is Orama?' },
// { role: 'assistant', content: 'Orama is a next-generation answer engine' }
Clearing the Session
You can clear the answer session by using the clearSession
method. This will reset the messages to an empty array:
answerSession . clearSession ();
answerSession. clearSession ()
answerSession. clearSession ()