🐝 BeeAI agent
Example of how to use Bee Agent Framework with Apify Actors to create a social media analysis agent.
src/main.ts
1import { Actor, log } from 'apify';2import { OpenAIChatModel } from 'beeai-framework/adapters/openai/backend/chat';3import { ReActAgent } from 'beeai-framework/agents/react/agent';4import { UnconstrainedMemory } from 'beeai-framework/memory/unconstrainedMemory';5import { z } from 'zod';6
7import { StructuredOutputGenerator } from './structured_response_generator.js';8import { CalculatorSumTool } from './tools/calculator.js';9import { InstagramScrapeTool } from './tools/instagram.js';10
11// Apify-hosted OpenRouter proxy (https://apify.com/apify/openrouter): an12// OpenAI-compatible endpoint billed against the user's Apify account, so no13// provider API key is needed. The proxy authenticates via the `Authorization`14// header carrying the run's APIFY_TOKEN.15const OPENROUTER_BASE_URL = 'https://openrouter.apify.actor/api/v1';16
17// This is an ESM project, and as such, it requires you to specify extensions in your relative imports.18// Read more about this here: https://nodejs.org/docs/latest-v18.x/api/esm.html#mandatory-file-extensions19// Note that we need to use `.js` even when inside TS files20// import { router } from './routes.js';21
22// Actor input schema23interface Input {24 query: string;25 modelName: string;26 debug?: boolean;27}28
29// The init() call configures the Actor to correctly work with the Apify-provided environment - mainly the storage infrastructure. It is necessary that every Actor performs an init() call.30await Actor.init();31
32// Charge for Actor start33await Actor.charge({ eventName: 'actor-start' });34
35// Handle input36const {37 // The query default value is provided only for template testing purposes.38 // You can remove it.39 query,40 modelName,41 debug,42} = (await Actor.getInput()) as Input;43if (debug) {44 log.setLevel(log.LEVELS.DEBUG);45}46if (!query) {47 throw new Error('An agent query is required.');48}49
50/**51 * Actor code52 */53// Create an agent that can use tools.54// See https://framework.beeai.dev/modules/agents55log.debug(`Using model: ${modelName}`);56const llm = new OpenAIChatModel(57 modelName,58 {},59 {60 baseURL: OPENROUTER_BASE_URL,61 apiKey: 'apify-openrouter-proxy', // unused by the proxy; OpenAI SDK rejects an empty string62 headers: { Authorization: `Bearer ${process.env.APIFY_TOKEN}` },63 },64);65const agent = new ReActAgent({66 llm,67 memory: new UnconstrainedMemory(),68 tools: [new CalculatorSumTool(), new InstagramScrapeTool()],69});70
71// Store tool messages for later structured output generation.72// This can be removed if you don't need structured output.73const structuredOutputGenerator = new StructuredOutputGenerator(llm);74
75// Prompt the agent with the query.76// Debug log agent status updates, e.g., thoughts, tool calls, etc.77const response = await agent.run({ prompt: query }).observe((emitter) => {78 emitter.on('update', async ({ update }) => {79 log.debug(`Agent (${update.key}) 🤖 : ${update.value}`);80
81 // Save tool messages for later structured output generation.82 // This can be removed if you don't need structured output.83 if (['tool_name', 'tool_output', 'tool_input'].includes(update.key as string)) {84 structuredOutputGenerator.processToolMessage(85 update.key as 'tool_name' | 'tool_output' | 'tool_input',86 update.value,87 );88 }89 // End of tool message saving.90 });91});92
93log.info(`Agent 🤖 : ${response.result.text}`);94
95// Hacky way to get the structured output.96// Using the stored tool messages and the user query to create a structured output.97const structuredResponse = await structuredOutputGenerator.generateStructuredOutput(98 query,99 z.object({100 totalLikes: z.number(),101 totalComments: z.number(),102 mostPopularPosts: z.array(103 z.object({104 url: z.string(),105 likes: z.number(),106 comments: z.number(),107 timestamp: z.string(),108 caption: z.string().nullable().optional(),109 alt: z.string().nullable().optional(),110 }),111 ),112 }),113);114log.debug(`Structured response: ${JSON.stringify(structuredResponse)}`);115
116// Charge for task completion117await Actor.charge({ eventName: 'task-completed' });118
119// Push results to the dataset.120await Actor.pushData({121 query,122 response: response.result.text,123 // This can be removed if you don't need structured output.124 structuredResponse: structuredResponse.object,125});126log.info('Pushed the data into the dataset!');127
128// Gracefully exit the Actor process. It's recommended to quit all Actors with an exit().129await Actor.exit();A template for BeeAI agent projects in TypeScript for building AI agents with Apify Actors . This template offers a structured setup and an example ReAct agent utilizing Instagram Scraper and a calculator tool in a workflow context.
A ReAct agent is employed, equipped with tools to respond to user queries. The agent processes a user query, decides on the tools to use, and in what sequence, to achieve the desired outcome. Here, the agent leverages an Instagram Scraper to fetch posts from a profile and a calculator tool to compute sums, such as totaling likes or comments. The agent produces textual and structured output, which is saved to a dataset.
The agent talks to its LLM through the Apify OpenRouter proxy — an OpenAI-compatible endpoint at https://openrouter.apify.actor/api/v1 that fronts the full OpenRouter model catalog. Token usage is billed against the user's Apify account (pay-per-event), so no OPENAI_API_KEY or any other provider API key is required. The Actor authenticates with the proxy using the APIFY_TOKEN that the platform injects into every run automatically.
If you'd rather call OpenAI / Anthropic / etc. directly with your own key, swap the OpenAIChatModel configuration in src/main.ts for a different baseURL / apiKey / provider adapter — see the BeeAI backend docs .
Add or modify tools in src/tools/calculator.ts and src/tools/instagram.ts, and register them in the agent's tool list in src/main.ts. You can also adjust the agent's system prompt or other configuration in src/main.ts. For more details, see the BeeAI framework agents documentation .
This template uses the Pay Per Event (PPE) monetization model, which provides flexible pricing based on defined events.
To charge users, define events in JSON format and save them on the Apify platform. Here is an example schema with the task-completed event:
[{"task-completed": {"eventTitle": "Task completed","eventDescription": "Cost per query answered.","eventPriceUsd": 0.1}}]
In the Actor, trigger the event with:
await Actor.charge({ eventName: 'task-completed' });
This approach allows you to programmatically charge users directly from your Actor, covering the costs of execution and related services, such as LLM input/output tokens.
To set up the PPE model for this Actor:
- Configure Pay Per Event: establish the Pay Per Event pricing schema in the Actor's Monetization settings. First, set the Pricing model to
Pay per eventand add the schema. An example schema can be found in pay_per_event.json.
No provider API key (e.g. OPENAI_API_KEY) needs to be configured — LLM costs are billed through the Apify OpenRouter proxy to the user running the Actor.
- Apify SDK for JavaScript - a toolkit for building Apify Actors and scrapers in JavaScript
- Input schema - define and easily validate a schema for your Actor's input
- Dataset - store structured data where each object stored has the same attributes
- Key-value store - store any kind of data, such as JSON documents, images, or text files
Crawlee + Cheerio
A scraper example that uses Cheerio to parse HTML. It's fast, but it can't run the website's JavaScript or pass JS anti-scraping challenges.
One‑Page HTML Scraper with Cheerio
Scrape single page with provided URL with Axios and extract data from page's HTML with Cheerio.
Crawlee + Puppeteer + Chrome
Example of a Puppeteer and headless Chrome web scraper. Headless browsers render JavaScript and are harder to block, but they're slower than plain HTTP.
Crawlee + Playwright + Chrome
Web scraper example with Crawlee, Playwright and headless Chrome. Playwright is more modern, user-friendly and harder to block than Puppeteer.
Crawlee + Playwright + Camoufox
Web scraper example with Crawlee, Playwright and headless Camoufox. Camoufox is a custom stealthy fork of Firefox. Try this template if you're facing anti-scraping challenges.
Playwright + Chrome Test Runner
Example of using the Playwright Test project to run automated website tests in the cloud and display their results. Usable as an API.