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
2 changes: 1 addition & 1 deletion integrations/browser/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { actionDefinitions } from 'src/definitions/actions'
export default new IntegrationDefinition({
name: 'browser',
title: 'Browser',
version: '0.7.0',
version: '0.8.0',
description:
'Capture screenshots and retrieve web page content with metadata for automated browsing and data extraction.',
readme: 'hub.md',
Expand Down
1 change: 1 addition & 0 deletions integrations/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"private": true,
"dependencies": {
"@botpress/sdk": "workspace:*",
"@mendable/firecrawl-js": "^4.3.8",
"axios": "^1.7.2"
},
"devDependencies": {
Expand Down
68 changes: 25 additions & 43 deletions integrations/browser/src/actions/browse-pages.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import { IntegrationLogger } from '@botpress/sdk'
import axios from 'axios'
import { IntegrationLogger, RuntimeError } from '@botpress/sdk'
import Firecrawl from '@mendable/firecrawl-js'
import { FullPage } from 'src/definitions/actions'
import * as bp from '.botpress'

type FireCrawlResponse = {
success: boolean
data: {
markdown: string
metadata: {
ogLocaleAlternate: string[]
favicon?: string
title?: string | string[] | null
description?: string | string[] | null
sourceURL: string
error: string | null
statusCode: number
}
}
}

const COST_PER_PAGE = 0.0015

const fixOutput = (val: unknown): string => {
Expand All @@ -37,37 +21,35 @@ const getPageContent = async (props: {
timeout?: number
maxAge?: number
}): Promise<FullPage> => {
const firecrawl = new Firecrawl({ apiKey: bp.secrets.FIRECRAWL_API_KEY })

const startTime = Date.now()
const { data: result } = await axios.post<FireCrawlResponse>(
'https://api.firecrawl.dev/v1/scrape',
{
url: props.url,

try {
const result = await firecrawl.scrape(props.url, {
fastMode: true,
onlyMainContent: true,
maxAge: 60 * 60 * 24 * 7, // 1 week
removeBase64Images: true,
waitFor: props.waitFor,
timeout: props.timeout,
maxAge: 60 * 60 * 24 * 7, // 1 week
},
{
headers: {
Authorization: `Bearer ${bp.secrets.FIRECRAWL_API_KEY}`,
},
}
)

const { metadata, markdown } = result.data
formats: ['markdown', 'rawHtml'],
storeInCache: true,
})

props.logger.forBot().info(`Browsing ${props.url} took ${Date.now() - startTime}ms`, {
size: markdown.length,
statusCode: metadata.statusCode,
error: metadata.error,
})
props.logger.forBot().debug(`Firecrawl API call took ${Date.now() - startTime}ms for url: ${props.url}`)

return {
url: props.url,
content: markdown,
favicon: fixOutput(metadata.favicon),
title: fixOutput(metadata.title),
description: fixOutput(metadata.description),
return {
url: props.url,
content: result.markdown!,
raw: result.rawHtml!,
favicon: fixOutput(result.metadata?.favicon),
title: fixOutput(result.metadata?.title),
description: fixOutput(result.metadata?.description),
}
} catch (err) {
props.logger.error('There was an error while calling Firecrawl API.', err)
throw new RuntimeError(`There was an error while browsing the page: ${props.url}`)
}
}

Expand Down
51 changes: 10 additions & 41 deletions integrations/browser/src/actions/discover-urls.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,13 @@
import { RuntimeError } from '@botpress/client'
import { IntegrationLogger, z, ZodIssueCode } from '@botpress/sdk'
import axios from 'axios'
import Firecrawl from '@mendable/firecrawl-js'
import { isValidGlob, matchGlob } from '../utils/globs'
import * as bp from '.botpress'

const LAMBDA_TIMEOUT = 55_000

const COST_PER_FIRECRAWL_MAP = 0.001

type FirecrawlMapInput = {
/** The base URL to start crawling from */
url: string
/** Search query to use for mapping. During the Alpha phase, the 'smart' part of the search functionality is limited to 1000 search results. However, if map finds more results, there is no limit applied. */
search?: string
/** Ignore the website sitemap when crawling */
ignoreSitemap?: boolean
/** Only return links found in the website sitemap */
sitemapOnly?: boolean
/** In milliseconds */
timeout?: number
/** Max 30_000 */
limit?: number
/** Defaults to true */
includeSubdomains?: boolean
}

type FireCrawlResponse = {
success: boolean
links: string[]
}

type StopReason = Awaited<ReturnType<bp.IntegrationProps['actions']['discoverUrls']>>['stopReason']

export const urlSchema = z.string().transform((url, ctx) => {
Expand Down Expand Up @@ -149,25 +127,16 @@ class Accumulator {
}

const firecrawlMap = async (props: { url: string; logger: IntegrationLogger; timeout: number }): Promise<string[]> => {
const { data: result } = await axios.post<FireCrawlResponse>(
'https://api.firecrawl.dev/v1/map',
{
url: props.url,
ignoreSitemap: false,
includeSubdomains: true,
sitemapOnly: false,
limit: 10_000,
timeout: Math.max(1000, props.timeout - 2000),
} satisfies FirecrawlMapInput,
{
signal: AbortSignal.timeout(Math.max(1000, props.timeout - 1000)),
headers: {
Authorization: `Bearer ${bp.secrets.FIRECRAWL_API_KEY}`,
},
}
)
const firecrawl = new Firecrawl({ apiKey: bp.secrets.FIRECRAWL_API_KEY })

const result = await firecrawl.map(props.url, {
sitemap: 'include',
limit: 10_000,
timeout: Math.max(1000, props.timeout - 2000),
includeSubdomains: true,
})

return result.links
return result.links.map((x) => x.url)
}

export const discoverUrls: bp.IntegrationProps['actions']['discoverUrls'] = async ({ input, logger, metadata }) => {
Expand Down
1 change: 1 addition & 0 deletions integrations/browser/src/definitions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const captureScreenshot: ActionDefinition = {
const fullPage = z.object({
url: z.string(),
content: z.string(),
raw: z.string(),
favicon: z.string().optional(),
title: z.string().optional(),
description: z.string().optional(),
Expand Down
2 changes: 1 addition & 1 deletion packages/cognitive/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botpress/cognitive",
"version": "0.1.44",
"version": "0.1.45",
"description": "Wrapper around the Botpress Client to call LLMs",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
Expand Down
4 changes: 4 additions & 0 deletions packages/cognitive/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class Cognitive {
protected _provider: ModelProvider
protected _downtimes: ModelPreferences['downtimes'] = []
protected _useBeta: boolean = false
protected _debug = false

private _events = createNanoEvents<Events>()

Expand All @@ -61,11 +62,14 @@ export class Cognitive {
provider: this._provider,
timeout: this._timeoutMs,
maxRetries: this._maxRetries,
__debug: this._debug,
__experimental_beta: this._useBeta,
})

copy._models = [...this._models]
copy._preferences = this._preferences ? { ...this._preferences } : null
copy._downtimes = [...this._downtimes]

copy.interceptors.request = this.interceptors.request
copy.interceptors.response = this.interceptors.response

Expand Down
19 changes: 19 additions & 0 deletions packages/cognitive/src/cognitive-v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ClientProps = {
botId?: string
token?: string
withCredentials?: boolean
debug?: boolean
headers?: Record<string, string>
}

Expand All @@ -21,12 +22,15 @@ type RequestOptions = {

const isBrowser = () => typeof window !== 'undefined' && typeof window.fetch === 'function'

export { Models } from './types'

export class CognitiveBeta {
private _axiosClient: AxiosInstance
private readonly _apiUrl: string
private readonly _timeout: number
private readonly _withCredentials: boolean
private readonly _headers: Record<string, string>
private readonly _debug: boolean = false

public constructor(props: ClientProps) {
this._apiUrl = props.apiUrl || 'https://api.botpress.cloud'
Expand All @@ -42,13 +46,28 @@ export class CognitiveBeta {
this._headers['Authorization'] = `Bearer ${props.token}`
}

if (props.debug) {
this._debug = true
this._headers['X-Debug'] = '1'
}

this._axiosClient = axios.create({
headers: this._headers,
withCredentials: this._withCredentials,
baseURL: this._apiUrl,
})
}

public clone(): CognitiveBeta {
return new CognitiveBeta({
apiUrl: this._apiUrl,
timeout: this._timeout,
withCredentials: this._withCredentials,
headers: this._headers,
debug: this._debug,
})
}

public async generateText(input: CognitiveRequest, options: RequestOptions = {}) {
const signal = options.signal ?? AbortSignal.timeout(this._timeout)

Expand Down
17 changes: 2 additions & 15 deletions packages/cognitive/src/cognitive-v2/types.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
type Models =
export type Models =
| 'auto'
| 'best'
| 'fast'
| 'reasoning'
| 'cheapest'
| 'balance'
| 'recommended'
| 'reasoning'
| 'general-purpose'
| 'low-cost'
| 'vision'
| 'coding'
| 'function-calling'
| 'agents'
| 'storytelling'
| 'preview'
| 'roleplay'
| 'anthropic:claude-3-5-haiku-20241022'
| 'anthropic:claude-3-5-sonnet-20240620'
| 'anthropic:claude-3-5-sonnet-20241022'
Expand Down Expand Up @@ -70,6 +56,7 @@ type Models =
| 'xai:grok-4-fast-reasoning'
| 'xai:grok-code-fast-1'
| ({} & string)

export type CognitiveRequest = {
/**
* @minItems 1
Expand Down
1 change: 1 addition & 0 deletions packages/cognitive/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type CognitiveProps = {
maxRetries?: number
/** Whether to use the beta client. Restricted to authorized users. */
__experimental_beta?: boolean
__debug?: boolean
}

export type Events = {
Expand Down
4 changes: 2 additions & 2 deletions packages/llmz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "llmz",
"type": "module",
"description": "LLMz – An LLM-native Typescript VM built on top of Zui",
"version": "0.0.25",
"version": "0.0.26",
"types": "./dist/index.d.ts",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
Expand Down Expand Up @@ -67,7 +67,7 @@
},
"peerDependencies": {
"@botpress/client": "^1.25.0",
"@botpress/cognitive": "^0.1.44",
"@botpress/cognitive": "^0.1.45",
"@bpinternal/thicktoken": "^1.0.5",
"@bpinternal/zui": "^1.0.1"
},
Expand Down
11 changes: 5 additions & 6 deletions packages/llmz/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Models } from '@botpress/cognitive'
import { z } from '@bpinternal/zui'
import { cloneDeep, isPlainObject } from 'lodash-es'
import { ulid } from 'ulid'
Expand All @@ -16,16 +17,14 @@ import { Transcript, TranscriptArray } from './transcript.js'
import { wrapContent } from './truncator.js'
import { ObjectMutation, Serializable, Trace } from './types.js'

export type Model = 'best' | 'fast' | `${string}:${string}` | (string & {})

export type IterationParameters = {
transcript: TranscriptArray
tools: Tool[]
objects: ObjectInstance[]
exits: Exit[]
instructions?: string
components: Component[]
model: Model
model: Models | Models[]
temperature: number
}

Expand Down Expand Up @@ -350,7 +349,7 @@ export namespace Iteration {
messages: LLMzPrompts.Message[]
code?: string
traces: Trace[]
model: Model
model: Models | Models[]
temperature: number
variables: Record<string, any>
started_ts: number
Expand Down Expand Up @@ -584,7 +583,7 @@ export class Context implements Serializable<Context.JSON> {
public objects?: ValueOrGetter<ObjectInstance[], Context>
public tools?: ValueOrGetter<Tool[], Context>
public exits?: ValueOrGetter<Exit[], Context>
public model?: ValueOrGetter<Model, Context>
public model?: ValueOrGetter<Models | Models[], Context>
public temperature: ValueOrGetter<number, Context>

public version: Prompt = DualModePrompt
Expand Down Expand Up @@ -910,7 +909,7 @@ export class Context implements Serializable<Context.JSON> {
exits?: ValueOrGetter<Exit[], Context>
loop?: number
temperature?: ValueOrGetter<number, Context>
model?: ValueOrGetter<Model, Context>
model?: ValueOrGetter<Models | Models[], Context>
metadata?: Record<string, any>
snapshot?: Snapshot
timeout?: number
Expand Down
Loading
Loading