Skip to content

Commit

Permalink
⚡ Add dynamic timeout to bot engine api
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Dec 8, 2023
1 parent 8819e9e commit 957eaf3
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 31 deletions.
6 changes: 5 additions & 1 deletion apps/viewer/src/features/chat/api/continueChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ export const continueChat = publicProcedure
logs,
lastMessageNewFormat,
visitedEdges,
} = await continueBotFlow(message, { version: 2, state: session.state })
} = await continueBotFlow(message, {
version: 2,
state: session.state,
startTime: Date.now(),
})

if (newSessionState)
await saveStateToDatabase({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const createSpeechOpenAI = async (
])

return {
startTimeShouldBeUpdated: true,
outgoingEdgeId,
newSessionState,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,24 @@ export const createChatCompletionOpenAI = async (
})
if (!chatCompletion)
return {
startTimeShouldBeUpdated: true,
outgoingEdgeId,
logs,
}
const messageContent = chatCompletion.choices.at(0)?.message?.content
const totalTokens = chatCompletion.usage?.total_tokens
if (isEmpty(messageContent)) {
console.error('OpenAI block returned empty message', chatCompletion.choices)
return { outgoingEdgeId, newSessionState }
return { outgoingEdgeId, newSessionState, startTimeShouldBeUpdated: true }
}
return {
...(await resumeChatCompletion(newSessionState, {
options,
outgoingEdgeId,
logs,
})(messageContent, totalTokens)),
startTimeShouldBeUpdated: true,
}
return resumeChatCompletion(newSessionState, {
options,
outgoingEdgeId,
logs,
})(messageContent, totalTokens)
}

const isNextBubbleMessageWithAssistantMessage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ type ParsedWebhook = ExecutableWebhook & {
isJson: boolean
}

export const responseDefaultTimeout = 10000
export const longRequestTimeout = 120000

const longReqTimeoutWhitelist = [
'https://api.openai.com',
'https://retune.so',
'https://www.chatbase.co',
]

export const executeWebhookBlock = async (
state: SessionState,
block: WebhookBlock | ZapierBlock | MakeComBlock | PabblyConnectBlock
Expand Down Expand Up @@ -64,14 +73,21 @@ export const executeWebhookBlock = async (
},
],
}
const { response: webhookResponse, logs: executeWebhookLogs } =
await executeWebhook(parsedWebhook)
return resumeWebhookExecution({
state,
block,
logs: executeWebhookLogs,
const {
response: webhookResponse,
})
logs: executeWebhookLogs,
startTimeShouldBeUpdated,
} = await executeWebhook(parsedWebhook)

return {
...resumeWebhookExecution({
state,
block,
logs: executeWebhookLogs,
response: webhookResponse,
}),
startTimeShouldBeUpdated,
}
}

const checkIfBodyIsAVariable = (body: string) => /^{{.+}}$/.test(body)
Expand Down Expand Up @@ -142,11 +158,19 @@ const parseWebhookAttributes =

export const executeWebhook = async (
webhook: ParsedWebhook
): Promise<{ response: WebhookResponse; logs?: ChatLog[] }> => {
): Promise<{
response: WebhookResponse
logs?: ChatLog[]
startTimeShouldBeUpdated?: boolean
}> => {
const logs: ChatLog[] = []
const { headers, url, method, basicAuth, body, isJson } = webhook
const contentType = headers ? headers['Content-Type'] : undefined

const isLongRequest = longReqTimeoutWhitelist.some((whiteListedUrl) =>
url?.includes(whiteListedUrl)
)

const request = {
url,
method: method as Method,
Expand All @@ -159,7 +183,11 @@ export const executeWebhook = async (
form:
contentType?.includes('x-www-form-urlencoded') && body ? body : undefined,
body: body && !isJson ? (body as string) : undefined,
timeout: {
response: isLongRequest ? longRequestTimeout : responseDefaultTimeout,
},
} satisfies OptionsInit

try {
const response = await got(request.url, omit(request, 'url'))
logs.push({
Expand All @@ -177,6 +205,7 @@ export const executeWebhook = async (
data: safeJsonParse(response.body).data,
},
logs,
startTimeShouldBeUpdated: isLongRequest,
}
} catch (error) {
if (error instanceof HTTPError) {
Expand All @@ -193,7 +222,7 @@ export const executeWebhook = async (
response,
},
})
return { response, logs }
return { response, logs, startTimeShouldBeUpdated: isLongRequest }
}
const response = {
statusCode: 500,
Expand All @@ -208,7 +237,7 @@ export const executeWebhook = async (
response,
},
})
return { response, logs }
return { response, logs, startTimeShouldBeUpdated: isLongRequest }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export const executeZemanticAiBlock = async (
} catch (e) {
console.error(e)
return {
startTimeShouldBeUpdated: true,
outgoingEdgeId: block.outgoingEdgeId,
logs: [
{
Expand All @@ -118,7 +119,11 @@ export const executeZemanticAiBlock = async (
}
}

return { outgoingEdgeId: block.outgoingEdgeId, newSessionState }
return {
outgoingEdgeId: block.outgoingEdgeId,
newSessionState,
startTimeShouldBeUpdated: true,
}
}

const replaceTemplateVars = (
Expand Down
12 changes: 10 additions & 2 deletions packages/bot-engine/continueBotFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@ import { getBlockById } from '@typebot.io/lib/getBlockById'
type Params = {
version: 1 | 2
state: SessionState
startTime?: number
}
export const continueBotFlow = async (
reply: string | undefined,
{ state, version }: Params
{ state, version, startTime }: Params
): Promise<
ContinueChatResponse & {
newSessionState: SessionState
Expand Down Expand Up @@ -127,7 +128,13 @@ export const continueBotFlow = async (
...group,
blocks: group.blocks.slice(blockIndex + 1),
} as Group,
{ version, state: newSessionState, visitedEdges, firstBubbleWasStreamed }
{
version,
state: newSessionState,
visitedEdges,
firstBubbleWasStreamed,
startTime,
}
)
return {
...chatReply,
Expand Down Expand Up @@ -165,6 +172,7 @@ export const continueBotFlow = async (
state: newSessionState,
firstBubbleWasStreamed,
visitedEdges,
startTime,
})

return {
Expand Down
35 changes: 30 additions & 5 deletions packages/bot-engine/executeGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
} from './parseBubbleBlock'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { VisitedEdge } from '@typebot.io/prisma'
import { env } from '@typebot.io/env'
import { TRPCError } from '@trpc/server'
import { ExecuteIntegrationResponse, ExecuteLogicResponse } from './types'

type ContextProps = {
version: 1 | 2
Expand All @@ -35,6 +38,7 @@ type ContextProps = {
currentLastBubbleId?: string
firstBubbleWasStreamed?: boolean
visitedEdges: VisitedEdge[]
startTime?: number
}

export const executeGroup = async (
Expand All @@ -46,13 +50,15 @@ export const executeGroup = async (
currentReply,
currentLastBubbleId,
firstBubbleWasStreamed,
startTime,
}: ContextProps
): Promise<
ContinueChatResponse & {
newSessionState: SessionState
visitedEdges: VisitedEdge[]
}
> => {
let newStartTime = startTime
const messages: ContinueChatResponse['messages'] =
currentReply?.messages ?? []
let clientSideActions: ContinueChatResponse['clientSideActions'] =
Expand All @@ -65,6 +71,17 @@ export const executeGroup = async (

let index = -1
for (const block of group.blocks) {
if (
newStartTime &&
env.CHAT_API_TIMEOUT &&
Date.now() - newStartTime > env.CHAT_API_TIMEOUT
) {
throw new TRPCError({
code: 'TIMEOUT',
message: `${env.CHAT_API_TIMEOUT / 1000} seconds timeout reached`,
})
}

index++
nextEdgeId = block.outgoingEdgeId

Expand Down Expand Up @@ -93,13 +110,20 @@ export const executeGroup = async (
logs,
visitedEdges,
}
const executionResponse = isLogicBlock(block)
? await executeLogic(newSessionState)(block)
: isIntegrationBlock(block)
? await executeIntegration(newSessionState)(block)
: null
const executionResponse = (
isLogicBlock(block)
? await executeLogic(newSessionState)(block)
: isIntegrationBlock(block)
? await executeIntegration(newSessionState)(block)
: null
) as ExecuteLogicResponse | ExecuteIntegrationResponse | null

if (!executionResponse) continue
if (
'startTimeShouldBeUpdated' in executionResponse &&
executionResponse.startTimeShouldBeUpdated
)
newStartTime = Date.now()
if (executionResponse.logs)
logs = [...(logs ?? []), ...executionResponse.logs]
if (executionResponse.newSessionState)
Expand Down Expand Up @@ -162,6 +186,7 @@ export const executeGroup = async (
logs,
},
currentLastBubbleId: lastBubbleBlockId,
startTime: newStartTime,
})
}

Expand Down
4 changes: 4 additions & 0 deletions packages/bot-engine/startBotFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ type Props = {
version: 1 | 2
state: SessionState
startFrom?: StartFrom
startTime?: number
}

export const startBotFlow = async ({
version,
state,
startFrom,
startTime,
}: Props): Promise<
ContinueChatResponse & {
newSessionState: SessionState
Expand All @@ -39,6 +41,7 @@ export const startBotFlow = async ({
version,
state: newSessionState,
visitedEdges,
startTime,
})
}
const firstEdgeId = getFirstEdgeId({
Expand All @@ -54,6 +57,7 @@ export const startBotFlow = async ({
version,
state: newSessionState,
visitedEdges,
startTime,
})
}

Expand Down
1 change: 1 addition & 0 deletions packages/bot-engine/startSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const startSession = async ({
state: initialState,
startFrom:
startParams.type === 'preview' ? startParams.startFrom : undefined,
startTime: Date.now(),
})

// If params has message and first block is an input block, we can directly continue the bot flow
Expand Down
1 change: 1 addition & 0 deletions packages/bot-engine/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type ExecuteLogicResponse = {
export type ExecuteIntegrationResponse = {
outgoingEdgeId: EdgeId | undefined
newSessionState?: SessionState
startTimeShouldBeUpdated?: boolean
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>

export type ParsedReply =
Expand Down
2 changes: 1 addition & 1 deletion packages/embeds/js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@typebot.io/js",
"version": "0.2.25",
"version": "0.2.26",
"description": "Javascript library to display typebots on your website",
"type": "module",
"main": "dist/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,20 @@ export const ConversationContainer = (props: Props) => {
setIsSending(false)
if (error) {
setHasError(true)
props.onNewLogs?.([
const errorLogs = [
{
description: 'Failed to send the reply',
details: error,
status: 'error',
},
])
]
await saveClientLogsQuery({
apiHost: props.context.apiHost,
sessionId: props.initialChatReply.sessionId,
clientLogs: errorLogs,
})
props.onNewLogs?.(errorLogs)
return
}
if (!data) return
if (data.lastMessageNewFormat) {
Expand Down
Loading

3 comments on commit 957eaf3

@vercel
Copy link

@vercel vercel bot commented on 957eaf3 Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

bot.clubedotrader.club
bot.comebackreward.com
bot.desconto-unico.com
bot.gameincrivel.store
bot.gamesimples.online
bot.louismarcondes.com
bot.perfaceacademy.com
bot.pratikmandalia.com
bot.sucessodigital.xyz
bot.t20worldcup.com.au
bot.whatsappweb.adm.br
bot2.mycompany.reviews
bot3.mycompany.reviews
bot4.mycompany.reviews
c23111azqw.nigerias.io
chat.footballmeetup.ie
conto.barrettamario.it
dieta.barrettamario.it
elitebetoficial.com.br
felipewelington.com.br
form.bridesquadapp.com
form.searchcube.com.sg
go.orodrigoribeiro.com
help.giversforgood.com
inadimplencia-zero.com
info.clickasuransi.com
jenifferrodrigues.club
kodawariab736.skeep.it
mdb.diego.progenbr.com
michaeljackson.riku.ai
myhacklucrativo.online
newvoice.vfx.marketing
oferta.ambulare.com.br
ofertas.triptop.com.br
premium.kandabrand.com
produtosdablack.online
psicologiacognitiva.co
recruit-leadmagnet.com
report.gratirabbit.com
resume.gratirabbit.com
sellmycardoncaster.com
sellmycarsheffield.com
sonianaconfeitaria.com
suporte.pedroallan.com
teambuilding.hidden.sg
tourbot.verinterior.es
83242573.actualizar.xyz
87656003.actualizar.xyz
88152257.actualizar.xyz
91375310.actualizar.xyz
app.youvisitedthis.site
arrivalx2.wpwakanda.com
atendimento-efetivo.fun
autopilot.autoforce.com
bot.blackboxtips.com.br
bot.chat-digital.online
bot.gamefantastico.club
bot.gameincrivel.online
bot.jogonobrasil.online
bot.jogoquelucra.online
bot.jogotecnologico.com
bot.messengerbet.online
bot.sergiolimajr.com.br
bot.support.leopage.com
bot.ultimatecamp.com.br
builder.plannerzest.com
viewer-v2-typebot-io.vercel.app
mdb.evento.equipeinterna.progenbr.com
bot.studiotecnicoimmobiliaremerelli.it
mdb.assessoria.boaventura.progenbr.com
mdb.assessoria.jtrebesqui.progenbr.com
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
gabinete.baleia.formulario.progenbr.com
mdb.assessoria.carreirinha.progenbr.com
chrome-os-inquiry-system.itschromeos.com
mdb.assessoria.paulomarques.progenbr.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com
mdb.assessoria.qrcode.ademir.progenbr.com
mdb.assessoria.qrcode.arthur.progenbr.com
mdb.assessoria.qrcode.danilo.progenbr.com
mdb.assessoria.qrcode.marcao.progenbr.com
mdb.assessoria.qrcode.marcio.progenbr.com
mdb.assessoria.qrcode.aloisio.progenbr.com
mdb.assessoria.qrcode.girotto.progenbr.com
mdb.assessoria.qrcode.marinho.progenbr.com
mdb.assessoria.qrcode.rodrigo.progenbr.com
mdb.assessoria.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.desideri.progenbr.com
mdb.assessoria.qrcode.fernanda.progenbr.com
mdb.assessoria.qrcode.jbatista.progenbr.com
mdb.assessoria.qrcode.mauricio.progenbr.com
mdb.assessoria.fernanda.regional.progenbr.com
mdb.assessoria.qrcode.boaventura.progenbr.com
mdb.assessoria.qrcode.jtrebesqui.progenbr.com
mdb.assessoria.qrcode.carreirinha.progenbr.com
mdb.assessoria.qrcode.paulomarques.progenbr.com
mdb.assessoria.qrcode.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.fernanda.regional.progenbr.com

@vercel
Copy link

@vercel vercel bot commented on 957eaf3 Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app
app.typebot.io

@vercel
Copy link

@vercel vercel bot commented on 957eaf3 Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.