Creating an Answer Session
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({ // optional userContext: "The user is a very skilled programmer but has never used Orama before.", // optional inferenceType: "documentation", // optional initialMessages: [], // optional events: { 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?",});
import OramaCloudClient
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, initialMessages: [], oramaClient: oramaClient,)answerSession = AnswerSession(params: answerParams)
import com.orama.client.OramaClient
@Serializabledata class MyDoc ( val title: String, val category: String, val path: String, val content: String, val section: String)
val client = OramaClient( apiKey = "YOUR_PUBLIC_API_KEY", endpoint = "YOUR_ENDPOINT_URL")
val answerParams = AnswerParams( oramaClient = client, serializer = MyDoc.serializer(), userContext = "The user is a very skilled programmer but has never used Orama before.")
val answerSession = AnswerSession(answerParams)
print("coming soon")
// coming soon
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.'})
import OramaCloudClient
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, initialMessages: [], oramaClient: oramaClient,)let answerSession = AnswerSession(params: answerParams)
import com.orama.client.OramaClient
val client = OramaClient(apiKey = "", endpoint = "" )
val answerParams = AnswerParams( oramaClient = client, serializer = MyDoc.serializer(), userContext = "The user is a very skilled programmer but has never used Orama before.")
val answerSession = AnswerSession(answerParams)
print("coming soon")
// coming soon
If we prefer, we can also use an object to provide a more structured context:
const answerSession = orama.createAnswerSession({ userContext: { name: 'John Doe', musicTaste: ['Rock', 'Punk', 'Metal'], customerSince: 2019 }})
import OramaCloudClient
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, initialMessages: [], oramaClient: orama,)let answerSession = AnswerSession(params: answerParams)
import com.orama.client.OramaClient
val client = OramaClient(apiKey = "", endpoint = "" )
val answerParams = AnswerParams( oramaClient = client, serializer = MyDoc.serializer(), userContext = "{ name: \"John Doe\", musicTaste: [\"Rock\", \"Punk\", \"Metal\"], customerSince: 2019 }")
val answerSession = AnswerSession(answerParams)
print("coming soon")
// coming soon
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" },];
import OramaCloudClient
let initialMessages = [ 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."),)
print("coming soon")
// coming soon
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> = { interactionId: string, query: string, response: string, relatedQueries: Nullable<string[]>, sources: Nullable<Results<T>>, translatedQuery: Nullable<SearchParams<AnyOrama>>, aborted: boolean, loading: boolean}
import OramaCloudClient
struct Interaction<T: Codable> { var interactionId: String var query: String var response: String var relatedQueries: [String]? var sources: SearchResults<T>? var translatedQuery: ClientSearchParams? var aborted: Bool = false var loading: Bool}
import com.orama.client.OramaClient
data class Interaction<T> ( val interactionId: String, val query: String, val response: String, val relatedQueries: List<String>?, val sources: List<Hit<T>>, val translatedQuery: Map<String, JsonElement>, val aborted: Boolean = false, val loading: Boolean)
print("coming soon")
// coming soon
Consequently, the onStateChange
event will return an array of these interactions:
const answerSession = orama.createAnswerSession({ events: { onStateChange: (state) => { if (state.every(interaction => !interaction.loading)) { console.log(state) } }, },});
await answerSession.ask({ term: "What is Orama?",});
// [// {// 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"],// sources: [// {// count: 15,// elapsed: { formatted: "78ms", raw: 78000000 },// hits: [// { document: { title: "What is Orama", ... } },// { document: { title: "How Orama works", ... } },// { document: { title: "Why Orama is the best", ... } }// ]// }// ],// translatedQuery: { term: "What is Orama?" },// aborted: false,// loading: false// }// ]
let answerSession = AnswerSession(params: answerParams) .on(event: .stateChange) { state in if state.allSatisfy({ !$0.loading }) { print(state) } }
try await answerSession.ask(params: AnswerParams.AskParams( query: "What is Orama?", userData: nil, related: nil ))
// [// {// 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"],// sources: [// {// count: 15,// elapsed: { formatted: "78ms", raw: 78000000 },// hits: [// { document: { title: "What is Orama", ... } },// { document: { title: "How Orama works", ... } },// { document: { title: "Why Orama is the best", ... } }// ]// }// ],// translatedQuery: { term: "What is Orama?" },// aborted: false,// loading: false// }// ]
val answerSession = AnswerSession(answerParams, events = object: AnswerEventListener<MyDoc> { override fun onStateChange(state: MutableList<Interaction<MyDoc>>) { println(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"],// sources: [// {// count: 15,// elapsed: { formatted: "78ms", raw: 78000000 },// hits: [// { document: { title: "What is Orama", ... } },// { document: { title: "How Orama works", ... } },// { document: { title: "Why Orama is the best", ... } }// ]// }// ],// translatedQuery: { term: "What is Orama?" },// aborted: false,// loading: false// }// ]
print("coming soon")
// coming soon
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({ term: "What is Orama?",});
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"],// sources: [// {// count: 15,// elapsed: { formatted: "78ms", raw: 78000000 },// hits: [// { document: { title: "What is Orama", ... } },// { document: { title: "How Orama works", ... } },// { document: { title: "Why Orama is the best", ... } }// ]// }// ],// translatedQuery: { term: "What is Orama?" },// aborted: false,// loading: false// }// ]
let answerSession = AnswerSession(params: answerParams)
try await answerSession.ask(params: AnswerParams.AskParams( query: "What is Orama?", userData: nil, related: nil))
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"],// sources: [// {// count: 15,// elapsed: { formatted: "78ms", raw: 78000000 },// hits: [// { document: { title: "What is Orama", ... } },// { document: { title: "How Orama works", ... } },// { document: { title: "Why Orama is the best", ... } }// ]// }// ],// translatedQuery: { term: "What is Orama?" },// aborted: false,// loading: false// }// ]
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"],// sources: [// {// count: 15,// elapsed: { formatted: "78ms", raw: 78000000 },// hits: [// { document: { title: "What is Orama", ... } },// { document: { title: "How Orama works", ... } },// { document: { title: "Why Orama is the best", ... } }// ]// }// ],// translatedQuery: { term: "What is Orama?" },// aborted: false,// loading: false// }// ]
print("coming soon")
// coming soon
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({ events: { onMessageChange: (messages) => { console.log(messages); // [ // { role: 'user', content: 'What is Orama?' }, // { role: 'assistant', content: 'Orama is a next-generation answer engine' } // ] }, },});
await answerSession.ask({ term: "What is Orama?",});
let answerSession = AnswerSession(params: answerParams) .on(event: .messageChange) { print($0) }
try await answerSession.ask(params: AnswerParams.AskParams( query: "What is Orama?", userData: nil, related: nil ))
// [// { 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) { println(data) }})
val answer = answerSession.ask(AskParams( query = "Query is Orama?"))
print("coming soon")
// coming soon
onMessageLoading
Gets triggered everytime an answer request starts or ends.
const answerSession = orama.createAnswerSession({ events: { onMessageLoading: (loading) => { if (loading) { console.log("Still loading the messages..."); } }, },});
await answerSession.ask({ term: "What is Orama?",});
let answerSession = AnswerSession(params: answerParams) .on(event: .onMessageLoading) { if $0 { print("Still loading the messages...") } }
try await answerSession.ask(params: AnswerParams.AskParams( query: "What is Orama?", userData: nil, related: nil ))
val answerSession = AnswerSession(answerParams, events = object: AnswerEventListener<MyDoc> { override fun onMessageLoading(loading: Boolean) { if (loading) { println("Still loading the messages...") } }})
val answer = answerSession.ask(AskParams( query = "Query is Orama?"))
print("coming soon")
// coming soon
onAnswerAborted
Gets triggered when an answer gets aborted.
const answerSession = orama.createAnswerSession({ events: { onAnswerAborted: (aborted) => { alert("The user has aborted this answer generation!"); }, },});
await answerSession.ask({ term: "What is Orama?",});
setTimeout(() => { answerSession.abortAnswer();}, 1000);
print("coming soon")
val abortHandler = AbortHandler()
val answerSession = AnswerSession(answerParams, events = object: AnswerEventListener<MyDoc> { override fun onAnswerAborted(aborted: Boolean) { if (aborted) { println("The user has aborted this answer generation!") } }}, abortHandler = abortHandler)
val answer = answerSession.ask(AskParams( query = "Query is Orama?"))
delay(2000)abortHandler.abort()
print("coming soon")
// coming soon
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({ events: { onSourceChange: (sources) => { console.log( `Got ${sources.count} sources in ${sources.elapsed.formatted}.` ); console.log( `Top three sources are: ${sources.hits .map((hit) => hit.document.title) .join(", ")}` );
// Got 15 sources in 78ms. // Top three sources are: What is Orama, How Orama works, Why Orama is the best }, },});
await answerSession.ask({ term: "What is Orama?",});
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( query: "What is Orama?", userData: nil, related: nil ))
// 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?"))
print("coming soon")
// coming soon
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({ events: { onQueryTranslated: (query) => { console.log(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?", userData: nil, related: nil ))
// { term: 'Halfpipe skateboard', where: { price: { lt: 100 } } }
val answerSession = AnswerSession(answerParams, events = object: AnswerEventListener<MyDoc> { override fun onQueryTranslated(query: Map<String, JsonElement>) { println(query) }})
val answer = answerSession.ask(AskParams( query = "What's the best skateboard for halfpipes under $100?"))
print("coming soon")
// coming soon
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({ term: "What is Orama?",});
console.log(answer);// Orama is a next-generation answer engine
let answer = try await answerSession.ask(params: AnswerParams.AskParams( query: "What is Orama?", userData: nil, related: nil))
val answer = answerSession.ask(AskParams( query = "What is Orama?"))
print("coming soon")
// coming soon
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({ term: "What is Orama?",});
for await (const msg of answer) { console.log(msg);}
// Orama// is a// next-gener// ation answer// engine
let answer = try await answerSession.askStream(params: AnswerParams.AskParams( query: "What is Orama?", userData: nil, related: nil))
for try await msg in answer { print(msg)}
val answer = answerSession.askStream(AskParams( query = "What is Orama?"), this)
answer.incoming { println(it)}
print("coming soon")
// coming soon
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();
print("coming soon")
val myAbortHandler = AbortHandler()
val answerSession = AnswerSession(answerParams, abortHandler = myAbortHandler)
val answer = answerSession.ask(AskParams( query = "Query is Orama?"))
delay(2000)myAbortHandler.abort()
print("coming soon")
// coming soon
This will trigger the onAnswerAborted
event, which will simply return a true
in its callback function:
const answerSession = orama.createAnswerSession({ events: { onAnswerAborted: (aborted) => { alert("The user aborted the answer session!"); }, },});
print("coming soon")
val answerSession = AnswerSession(answerParams, events = object: AnswerEventListener<MyDoc> { override fun onAnswerAborted(aborted: Boolean) { if (aborted) { println("The user has aborted this answer generation!") } }}, abortHandler = abortHandler)
print("coming soon")
// coming soon
Getting related queries
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({ events: { onRelatedQueries: (relatedQueries) => { console.log(relatedQueries); // ["How Orama works", "Why Orama is the best", "Vector search with Orama"] }, }});
await answerSession.ask({ term: "What is Orama?", related: { howMany: 3, // How many related queries you want to get. Maximum is 5. format: 'query' }});
let answer = try await answerSession.ask(params: AnswerParams.AskParams( query: "What is Orama?", userData: "The user is a beginner in programming", related: AnswerParams<MyDoc>.RelatedQueries(howMany: 3, format: "query") ))
print("coming soon")
print("coming soon")
// coming soon
Getting all the messages
At any point in time, you can retrieve all the messages by calling the .getMessages
function:
const messages = answerSession.getMessages();
console.log(messages);
// [// { role: 'user', content: 'What is Orama?' },// { role: 'assistant', content: 'Orama is a next-generation answer engine' }// ]
let messages = answerSession.getMessages()
print(messages)
// [// { 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' }// ]
print("coming soon")
// coming soon
Regenerating the last answer
Sometimes, the user may want to regenerate the last answer. You can do this by calling the .regenerateLast
method:
// Ask the questionawait answerSession.ask({ term: "What is Orama?" });
// Regenerate the answer// "stream" is true by default, but you can set it to false if you want to get the entire answer at onceawait answerSession.regenerateLast({ stream: false });
// Check the messagesconst messages = answerSession.getMessages();
console.log(messages);
// [// { role: 'user', content: 'What is Orama?' },// { role: 'assistant', content: 'Orama is a next-generation answer engine' }// ]
// Ask the questionlet answer = try await answerSession.ask(params: AnswerParams.AskParams( query: "What is Orama?", userData: nil, related: nil))
// Regenerate the answer// "stream" is true by default, but you can set it to false if you want to get the entire answer at oncelet answer = try await answerSession.regenerateLast(stream: false)
// Check the messageslet messages = answerSession.getMessages()
print(messages)
// [// { role: 'user', content: 'What is Orama?' },// { role: 'assistant', content: 'Orama is a next-generation answer engine' }// ]
var answer = answerSession.ask(AskParams( query = "What is Orama?"))
answer = answerSession.regenerateLast()
val messages = answerSession.getMessages()
// [// { role: 'user', content: 'What is Orama?' },// { role: 'assistant', content: 'Orama is a next-generation answer engine' }// ]
print("coming soon")
echo "coming soon"
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()
print("coming soon")
// coming soon