MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

Leveling up OpenCode... and not in the way you would expect.

2026-08-22 08:44:02

So I've been using OpenCode for a while now, and it's pretty cool. It's clean, minimal, effective, and not hacking other companies with rogue AI bots 😅. But there is one thing that I dislike about all of these AI tools besides people using them wrong: it's all 1 prompt, 1 agent at a time.

Even with these new crazy models such as Kimi K3, Claude Fable 5, GPT Sol, DeepSeek V4 Pro, and the list goes on, having reliable workflows/pipelines is the best way to use AI effectively. Even these models that seem to be the "best" have pretty major flaws. Whether it is hardly speaking in an understandable way or just lying to your face, AI can be pretty annoying. I mean, they literally have "peak hours" and then "dumb hours" depending on the time zone. All of these are reasons why I just built an open-sourced project to fix this.

A little while ago, I discovered node-based workflows. Like I said earlier, using one agent one prompt at a time felt super unproductive, so I was inspired to fork OpenCode's harness and create my own twist on it. It still follows the concept of BYOK keys and using any provider you want, but instead of simply prompting, you build a workflow that you can easily save to reuse over and over again. How it works is you create a card for an agent, specify their role (planner, architect, coder, etc), and connect them to another agent or a chain of agents. Now it's not just Opus 5 doing everything, but every agent having a designated role and working together. You can make it as simple or complex as you want, and fork it so that it fits your needs.

That's all I have to say. I am still working on it and constantly improving it. Feel free to fork it and make it your own as well, and I hope that this tool levels up how you use AI.

How to Review AI-Generated SQL Before You Trust the Number

2026-08-22 08:43:48

An AI assistant will write you a query in ten seconds, the query will run, and the number that comes back will look completely reasonable. This page gives you the five checks that tell you whether that number is right. They take about two minutes, they need no tools beyond the database you already have, and they catch the four mistakes AI-written SQL actually makes.

The order matters. The checks are arranged cheapest first, so the first one costs a single row count and the last one costs a short conversation. Most wrong queries fall to the first two.

The short version. A query that runs has only passed a grammar check. The number is right when the rows, the filters and the denominator match the question you asked.

The database only takes a query as far as the first gate.

Why a query that runs can still be wrong

Before the list: what do you think the database actually checks when it accepts a query?

Grammar. That is the whole list. Spell a table name wrong and you get an error. Sum the wrong column, join in a way that doubles rows, or filter after grouping when the question needed it before, and you get a clean result set with a wrong number in it. Every mistake on this page is valid SQL.

AI assistants add one specific difficulty: their queries are fluent. The aliases are tidy, the formatting is clean, and the shape looks like something a careful person wrote. Fluency reads as correctness, and it is not the same thing. Treat an AI query the way you would treat a first draft from a new colleague: with respect, and with the row counts open.

The table the examples run on

Everything below runs on one small shop dataset, so every number can be checked by hand. Thirteen orders in July, five customers, and a refunds table where two orders were refunded in two parts. Eleven of the thirteen orders are completed; one is refunded, one is pending. There is also a staff_accounts table listing internal accounts, and it contains one NULL row, because real lookup tables usually do.

The gross value of the eleven completed orders is 1,605. Total refunds are 275. Hold on to those two numbers.

Check 1: count the rows before you trust the sum

Before the answer: eleven completed orders, five refund rows. After a LEFT JOIN from orders to refunds, does the query see eleven rows, or more?

Here is the query an assistant wrote for "net revenue from completed orders":

SELECT SUM(o.amount) - SUM(COALESCE(r.refund_amount, 0)) AS net_revenue
FROM orders o
LEFT JOIN refunds r ON r.order_id = o.order_id
WHERE o.status = 'completed';

It runs. It returns 1,830. The right answer is 1,330 , which you already know, because 1,605 minus 275 is 1,330.

The join is the problem. Two orders were each refunded in two parts, so each of those orders matches two refund rows. The join turns eleven rows into thirteen, and SUM(o.amount) counts those two orders twice: 2,105 instead of 1,605. The extra 500 is exactly the value of the two double-counted orders. This is called fan-out: a join multiplies rows whenever the key on the other side appears more than once.

The check costs two counts:

SELECT COUNT(*) FROM orders WHERE status = 'completed';        -- 11

SELECT COUNT(*)
FROM orders o LEFT JOIN refunds r ON r.order_id = o.order_id
WHERE o.status = 'completed';                                   -- 13

That one comparison decides it. If the second number grew, the join fanned out and every SUM or AVG over the left table's columns is suspect. If it held, the join is safe and you move on.

Check 2: look for NULL in every filter

The next request was "the same revenue, excluding staff accounts". The assistant wrote:

SELECT SUM(amount)
FROM orders
WHERE status = 'completed'
  AND customer_id NOT IN (SELECT customer_id FROM staff_accounts);

This returns NULL , from zero rows. Not a smaller number. Nothing.

Say out loud why one NULL in staff_accounts could empty the whole result, before reading on.

Here is the mechanism. NOT IN asks, for each order, "is this customer different from every value in the list?" One of the values in the list is NULL, and SQL cannot say whether anything is different from NULL. The comparison comes back unknown, unknown is not true, and no row survives. One NULL row in a lookup table silently empties the result.

The fix is either to keep NULL out of the list, or to use NOT EXISTS, which does not have this behavior:

SELECT SUM(amount)
FROM orders o
WHERE o.status = 'completed'
  AND NOT EXISTS (SELECT 1 FROM staff_accounts s
                  WHERE s.customer_id = o.customer_id);   -- 1,395

The reviewer's habit: for every column a filter touches, ask what happens to that filter when the column is NULL. The same blindness sinks = NULL, which is covered in NULL in SQL.

Check 3: ask where the filter sits, WHERE or HAVING

The request was "customers who spent more than 400 on completed orders". Which condition should remove rows before the grouping, and which should test the finished totals?

The assistant's version:

SELECT c.name, SUM(o.amount) AS total
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
GROUP BY c.name
HAVING SUM(o.amount) > 400;

It returns three customers: Ellis at 450, Diaz at 420, Boone at 420. The right answer is Ellis alone. Diaz only crosses 400 because a pending order was counted. Boone only crosses it because a refunded order was counted. The query never filtered on status, so the grouping summed everything.

The reviewed version filters rows first, then tests the totals:

SELECT c.name, SUM(o.amount) AS total
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
WHERE o.status = 'completed'
GROUP BY c.name
HAVING SUM(o.amount) > 400;                        -- Ellis, 450

The rule to review against: WHERE decides which rows are allowed into the groups, HAVING decides which finished groups are allowed into the result. An AI query that mentions a status, a date range or a segment only in HAVING, or not at all, deserves a second look. The full mechanics are in GROUP BY and HAVING.

Check 4: name the denominator

The request was "average order value for completed orders". Two queries, both fluent, both running clean:

-- version A
SELECT AVG(amount) FROM orders WHERE status = 'completed';

-- version B
SELECT AVG(cust_avg) FROM (
  SELECT AVG(amount) AS cust_avg
  FROM orders
  WHERE status = 'completed'
  GROUP BY customer_id
);

Version A returns 145.91. Version B returns 152.50. Neither is broken. They divide by different things. A divides the total by eleven orders. B averages five per-customer averages, which hands a customer with three small orders the same weight as a customer with two large ones.

Which one is right depends entirely on the question. "What does a typical order look like" is A. "What does a typical customer's order look like" is B. The assistant picked one without asking, because it had to pick something. The reviewer's question is always the same: divided by what? If you cannot answer it from the query, the query is not done. The same trap in spreadsheet form is in percentages and pivot tables.

Check 5: make the AI read the query back

The four checks above are mechanical. The last one catches everything else, and it uses the assistant itself.

Ask it to restate, clause by clause, what the query does and why each clause serves the question you asked. Not a summary. One line per clause, in the query's own order. A wrong paraphrase points at the wrong clause with surprising reliability, because the model has to commit to a claim about each piece instead of describing the whole.

This is the same read-out-loud block from the teaching-comment format, used as a review tool. The reviewed query from Check 1 looks like this when it carries its comment:

/* WHY: Net July revenue from completed orders.
   Refunds arrive in parts, so refunds are totaled
   per order BEFORE the join. Joining the raw refunds
   table doubles multi-refund orders (13 rows vs 11). */

WITH refund_totals AS (
  SELECT order_id, SUM(refund_amount) AS refunded
  FROM refunds
  GROUP BY order_id
)
SELECT SUM(o.amount) - SUM(COALESCE(rt.refunded, 0)) AS net_revenue
FROM orders o
LEFT JOIN refund_totals rt ON rt.order_id = o.order_id
WHERE o.status = 'completed';                       -- 1,330

Picture the last query an AI wrote for you at work. Walk it through Check 1 in your head: what would the row count be before its join, and after? If you cannot answer from memory, that is the query to run the checks on tomorrow.

Edge cases worth knowing

DISTINCT inside an aggregate is a signal, not a fix. When an AI writes SUM(DISTINCT amount), it usually met fan-out and silenced the symptom. Two different orders for 75 collapse into one, and the total is wrong in a new direction. Pre-aggregate in a CTE instead, as in Check 5.

Sometimes fan-out is the point. Joining orders to line items should multiply rows, because the question lives at line level. The check is not "did rows grow", it is "did rows grow when the question did not ask them to".

The NULL behavior of NOT IN is standard SQL, not a quirk of one engine. SQLite, PostgreSQL, MySQL and SQL Server all do it. Fixing it by cleaning the lookup table works until the next import adds a NULL back. NOT EXISTS stays fixed.

Where this comes from

The premise of this page, that AI SQL runs but is often wrong, is measured, not anecdotal. On the BIRD benchmark, 12,751 questions over 95 real databases, the strongest model tested in 2023 reached 54.89 percent execution accuracy. Human engineers reached 92.96 percent on the same questions. Execution accuracy means the query's result matched the correct result, so nearly every failure in that gap is a query that ran and returned a wrong answer (Li et al., 2023, Advances in Neural Information Processing Systems 36, Datasets and Benchmarks track). Models have improved since, and the gap has narrowed, not closed. The checks on this page are aimed at the failure modes that benchmark surfaced: wrong joins, wrong filters, wrong aggregation grain.

How to apply this to your own work

  1. Put the five checks somewhere you can see them: rows, NULL, WHERE/HAVING, denominator, read-back.
  2. For one week, run Checks 1 and 2 on every AI query before you use its number. They cost a minute.
  3. When a check fails, do not patch the symptom. Ask the assistant to explain the failing clause, then fix the cause.
  4. Keep the WHY header on the fixed query, so the next reader inherits the reasoning and not just the SQL.
  5. Do not try to retrofit every AI query already in your files. That is a miserable job. Review them as they come back up, one at a time.

If you have paper nearby, sketch the orders and refunds tables from Check 1 and draw one line from each completed order to its refund rows. The two orders that get two lines are the whole story of fan-out, and having drawn it once, you will see it in a query before you run it.

Cheat sheet

Check Run or ask Failing looks like
1. Rows COUNT(*) before and after each join Row count grew; sums over the left table inflated
2. NULL What is NULL in each filtered column? NOT IN returns nothing; = NULL matches nothing
3. Filter seat Is each condition in WHERE or HAVING, and should it be? Status or date named only after grouping, or missing
4. Denominator Divided by what? Average of averages; percentage of the wrong whole
5. Read-back One line per clause, against the question A clause the paraphrase gets wrong or skips

The one habit to keep

Count the rows before and after every join. It is the cheapest check on the page, it catches the most expensive mistake, and it works on human SQL exactly as well as it works on the machine's.

What is the most recent number an AI handed you that you passed along without checking, and which of the five would have caught it if it was wrong?

Every number here was run before it was published. The dataset is small on purpose, so you can rebuild it and check each result by hand. The 1,830, the empty result, the three-customer list and both averages are real outputs, not illustrations.

Want the wider skill rather than the checklist? SQL for Analysts reads queries line by line in everyday words, which is the habit these checks are built from. SQL for Analysts, $19 →

Originally published on Analyst Prep Kit: How to Review AI-Generated SQL Before You Trust the Number

Visit the site for more beginner data analysis guides and free resources: the full guide archive covers SQL, Excel, Power BI, Tableau, Python and statistics, and the practice kits run in your browser with nothing to install.

If it was useful: Buy Me a Coffee.

How to launch an AI automation agency offering voice AI agents for local businesses

2026-08-22 08:32:35

You'll build a repeatable service that lets plumbers, dentists, and other service-business owners answer calls with a natural-sounding, AI-driven voice that schedules appointments, qualifies leads, and captures payments. The result is a hands-free phone front-desk that you can sell as a monthly subscription and use to acquire new clients for your agency.

What you'll get: a working n8n workflow that wires Anthropic's Claude, ElevenLabs text-to-speech, and Twilio Programmable Voice together, plus a go-to client-acquisition script that turns the service into a scalable AI automation agency.

What you need

Tool Plan / Price* Role
n8n (self-hosted Docker) Free (self-hosted) - see Docker Hub for latest image Orchestrates API calls, stores conversation state
Twilio Programmable Voice Pay-as-you-go - check Twilio pricing page Provides inbound phone numbers and SIP bridge
Anthropic Claude API Usage-based - check Anthropic pricing page Generates conversational replies
ElevenLabs TTS API Usage-based - check ElevenLabs pricing page Turns Claude's text into a lifelike voice
Cloudflare DNS + SSL Free tier available - verify limits Publishes a secure webhook for Twilio
Git (optional) Free Version-controls workflow JSON

*We avoid stating exact free-tier caps; always verify the current provider pricing.

Estimated time-to-build: 12-16 hours total (including testing and client-onboarding script).

Defining the core pieces

Voice AI is the combination of speech-to-text, natural-language generation, and text-to-speech that lets a computer hold a phone conversation. In this guide we skip the speech-to-text step by letting Twilio forward the caller's audio to our n8n webhook; the rest happens via APIs.

Key insight: The biggest revenue lever for an AI automation agency is the repeatable client-acquisition funnel, not the underlying technology.

Building voice ai agents for local businesses

Below is a step-by-step walkthrough. Every step mentions the exact UI field, API endpoint, or n8n node name so you can copy-paste without guessing.

1. Set up a public HTTPS endpoint for n8n

  1. Deploy n8n with Docker:
docker run -d \
 --name n8n \
 -p 5678:5678 \
 -e N8N_BASIC_AUTH_ACTIVE=true \
 -e N8N_BASIC_AUTH_USER=admin \
 -e N8N_BASIC_AUTH_PASSWORD=CHANGE_ME \
 n8nio/n8n:latest
  1. Point a subdomain (e.g., voice.youragency.com) to your server's IP in Cloudflare DNS.
  2. Enable Full (Strict) SSL in Cloudflare and add a Page Rule that forwards https://voice.youragency.com/webhook to your n8n port 5678.

When you browse to https://voice.youragency.com/webhook, you should see a JSON "Welcome" response - that tells you the endpoint is reachable.

2. Register a Twilio phone number

  1. Log in to the Twilio Console and buy a local number - choose a Voice-enabled line.
  2. Under Configure > Voice & Fax, set A CALL COMES IN to Webhook and paste the full URL you just created, e.g., https://voice.youragency.com/webhook.
  3. Save. Twilio will now POST a CallSid and raw audio stream to your n8n webhook every time the number is dialed.

3. Create the n8n workflow

Open n8n, click New Workflow, and add the following nodes in order:

Node Purpose
Webhook Receives Twilio's inbound call payload
Set (named Extract Speech) Pulls CallSid and From fields into variables
HTTP Request (Anthropic) Sends the conversation transcript to Claude and gets a text reply
HTTP Request (ElevenLabs) Sends Claude's reply text to ElevenLabs TTS and receives an audio URL
Twilio (Make Call) Plays the TTS audio back to the caller
NoOp (End) Returns a 200 OK to Twilio

Configure the Anthropic request node

What this does: Calls Claude with a prompt that includes the latest caller input and a short service-specific script.

{
 "name": "Anthropic Claude",
 "type": "n8n-nodes-base.httpRequest",
 "position": [600,200],
 "parameters": {
 "url": "https://api.anthropic.com/v1/complete",
 "method": "POST",
 "jsonParameters": true,
 "options": {
 "bodyContentType": "json"
 },
 "bodyParametersJson": {
 "model": "claude-3-sonnet-20240229",
 "max_tokens": 150,
 "temperature": 0.3,
 "prompt": "You are a friendly office assistant for a local {{ $json.service_type }}. The caller says: \"{{ $json.caller_text }}\". Respond with a concise answer, ask for the needed information (e.g., appointment date), and keep the tone professional."
 },
 "authentication": "headerAuth",
 "headerAuth": {
 "name": "Authorization",
 "value": "Bearer {{ $env.ANTHROPIC_API_KEY }}"
 }
 }
}

Why it matters: The prompt is service-specific ({{ $json.service_type }}) so the same workflow can serve plumbers, dentists, or any other service business automation client.

Configure the ElevenLabs request node

{
 "name": "ElevenLabs TTS",
 "type": "n8n-nodes-base.httpRequest",
 "position": [900,200],
 "parameters": {
 "url": "https://api.elevenlabs.io/v1/text-to-speech/{{ $env.ELEVENLABS_VOICE_ID }}/stream",
 "method": "POST",
 "jsonParameters": false,
 "options": {
 "bodyContentType": "raw",
 "responseFormat": "json"
 },
 "bodyParametersJson": {
 "text": "{{ $node['Anthropic Claude'].json.completion }}"
 },
 "authentication": "headerAuth",
 "headerAuth": {
 "name": "xi-api-key",
 "value": "{{ $env.ELEVENLABS_API_KEY }}"
 }
 }
}

The node streams the generated audio back to Twilio in the next step.

Play the audio with Twilio

Add a Twilio node (type Make Call) and set:

  • To: {{ $json.From }} (the original caller)
  • From: your Twilio number
  • Twiml:
<Response>
 <Play>{{ $node['ElevenLabs TTS'].json.audio_url }}</Play>
</Response>

When the workflow finishes, the caller hears the AI-generated response as if a human were on the line.

4. Add a simple CRM hook (optional)

Create a second HTTP Request node that POSTs the call details to a Google Sheet via Zapier or directly to HubSpot. This gives you an automatic client acquisition log for each conversation, letting you track lead quality and invoice per-call usage.

5. Package the service for resale

  1. Write a one-page PDF that explains the benefit ("24/7 phone answering, zero staff") and bundles the monthly Twilio, Anthropic, and ElevenLabs fees.
  2. Host the PDF at https://getaab.com/free and link it from your agency landing page - this is the lead magnet that converts curious local business owners into paying clients.
  3. Use the internal guide AI automations you can sell (https://getaab.com/ai-automations-to-sell) to outline pricing tiers and upsell options (e.g., multilingual agents, call analytics).

Where this breaks

Failure mode Symptom Fix
Twilio webhook not reachable 11200 "HTTP retrieval failure" in Twilio console Verify Cloudflare DNS, ensure port 5678 is open, and that the HTTPS cert is valid.
Anthropic rate limit 429 error in n8n logs, no reply text Implement a n8n Retry node with exponential back-off; consider upgrading to a higher usage plan.
ElevenLabs voice ID change 404 from ElevenLabs, empty audio_url Store the voice ID as an environment variable and update it whenever you create a new custom voice.
Expired API keys 401 Unauthorized from either API Rotate keys regularly; add a Cron node that emails you when a request returns 401.
Cost blow-out Monthly invoice > forecast Set usage alerts in Twilio, Anthropic, and ElevenLabs dashboards; cap the number of calls per client in the n8n workflow with a IF node.
Caller hangs up before TTS finishes Twilio logs "Call ended" with no audio played Reduce max_tokens in the Claude prompt and enable EleventLabs streaming to lower latency.

Bottom line: Most breakages are network or quota-related; proactive monitoring stops surprise bills before they happen.

For a deeper technical reference, see n8n's documentation.

FAQ

How do I choose the right voice for a plumber versus a dentist?

Pick a voice that matches the brand tone: a warm, confident male voice for a plumber, a friendly female voice for a dentist. ElevenLabs lets you audition voices in the dashboard; select the voice_id and store it in the ELEVENLABS_VOICE_ID env var.

Can I add speech-to-text so the AI can understand caller intent?

Yes. Replace the Set node with a Twilio Gather action that records the caller's speech, then send the audio file to a STT service such as Google Speech-to-Text. The transcript becomes caller_text for the Claude prompt.

What does the client-acquisition funnel look like for this agency?

  1. Run a Facebook ad targeting local service businesses.
  2. Direct interested owners to the free guide (https://getaab.com/free).
  3. Offer a 14-day trial of the voice AI agent, billed automatically via Stripe after the trial.
  4. Use the CRM webhook to track conversion from trial to paid.

How much does it cost per call on average?

You'll pay per-minute for Twilio inbound minutes, per-token for Anthropic, and per-character for ElevenLabs. The exact amount varies with call length and prompt complexity; consult each provider's pricing page for up-to-date rates.

Is self-hosting the workflow a requirement?

No. You can run n8n in the cloud (e.g., n8n.cloud) if you prefer not to maintain a server, but self-hosting eliminates recurring SaaS fees and gives you full control over data privacy - critical for many service business automation clients.

Ready to start building your voice ai agents for local businesses service? Grab the free guide, set up the workflow, and begin acquiring plumbers, dentists, and other local pros today.

Get the free guide now →

Internal resources:

how to build voice ai for inbound calls

2026-08-22 08:32:08

You can have a Vapi agent answer every inbound call, ask qualifying questions, and hand the prospect off to Calendly to lock in a meeting - all without writing a single line of custom telephony code. The result is a self-contained voice AI agent that routes calls, captures lead data, and books calendar slots automatically.

voice is the audible sound produced by a human speaker that can be captured, transmitted, and synthesized by software.
voice AI agent is a software component that receives spoken input over a phone line, runs speech-to-text, applies a language model, and returns synthesized speech to the caller.

Below you'll find everything you need to reproduce the exact workflow, from the required services to the n8n JSON that creates the Vapi agent, plus the pitfalls that usually bite new builders.

What you need

Tool Plan / Price Role
Vapi Free tier or paid plan - check the Vapi pricing page Voice AI platform that hosts the conversational model and performs voice synthesis
Twilio Pay-as-you-go voice minutes - check Twilio pricing Provides the inbound phone number and SIP termination for Vapi
Calendly Free tier or paid plan - check Calendly pricing Calendar link generator and meeting scheduler
n8n (self-hosted) Community edition - free (Docker) Orchestrates the webhook chain between Vapi, Twilio, and your CRM
HubSpot CRM (optional) Free tier - check HubSpot pricing Stores qualified lead details for follow-up

Estimated build time: 1-2 days for a minimal production-ready flow, assuming you already have accounts for the services above.

how to build voice ai for inbound calls

The core of the solution is a Vapi "agent" that runs a scripted dialogue, a Twilio phone number that forwards calls to Vapi, and an n8n workflow that receives the webhook payload, enriches the lead, and creates a Calendly event. Follow each numbered step precisely; the configuration values are written exactly as they appear in the UI.

1. Provision a Twilio phone number

  1. Log into the Twilio Console and navigate to Phone Numbers → Manage → Active Numbers.
  2. Click Buy a Number, select a local US number, and press Buy.
  3. In the Configure tab for the new number, set Voice & Fax → A CALL COMES IN to Webhook and paste the URL that n8n will expose later (e.g., https://your-n8n-instance.com/webhook/vapi-inbound).
  4. Save the changes.

Tip: Twilio will send a POST request with CallSid, From, and To on every inbound call. n8n will use those fields to correlate the call with Vapi.

2. Create a Vapi agent that qualifies leads

Vapi agents are defined via a JSON payload that describes the prompt, voice synthesis settings, and webhook callbacks. Use the Vapi dashboard or API; the snippet below is the API version for reproducibility.

What this does: Sends a POST request to Vapi's /v1/agents endpoint, creating an agent that asks the caller for name, company, and a brief need description, then forwards the captured slots to a webhook.

curl -X POST https://api.vapi.ai/v1/agents \
 -H "Authorization: Bearer YOUR_VAPI_API_KEY" \
 -H "Content-Type: application/json" \
 -d '{
 "name": "Lead Qualifier",
 "voice": "en-US-Standard-C",
 "prompt": {
 "system": "You are a friendly sales development rep. Greet the caller, ask for name, company, and a short description of their challenge. Then say: I will send you a link to book a time with our specialist.",
 "temperature": 0.7
 },
 "slots": [
 {"name": "caller_name", "type": "string", "question": "May I have your name?"},
 {"name": "company", "type": "string", "question": "Which company are you representing?"},
 {"name": "challenge", "type": "string", "question": "Briefly describe the problem you want to solve."}
 ],
 "on_complete": {
 "webhook_url": "https://your-n8n-instance.com/webhook/vapi-complete",
 "method": "POST"
 }
 }'

Replace YOUR_VAPI_API_KEY with the secret you generate in the Vapi dashboard under API Keys. After a successful call, the response contains an agent_id; copy that value for the next step.

3. Wire Twilio to Vapi

Now tell Twilio to forward the call audio to the Vapi agent you just created.

  1. In the Twilio Console, open the phone number's Voice configuration again.
  2. Change A CALL COMES IN from Webhook to Twiml Bin.
  3. Create a new Twiml Bin with the following XML, inserting the AGENT_ID you recorded:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
 <Dial>
 <Sip>sip:[email protected]</Sip>
 </Dial>
</Response>

Save the Twiml Bin and associate it with the phone number. Twilio now streams the call directly into the Vapi agent, which will run the qualification script defined earlier.

4. Set up the n8n webhook for Vapi completion

n8n will receive the lead data once the Vapi dialogue ends, enrich it, push it to HubSpot (optional), and generate a Calendly link.

  1. Create a new workflow in n8n and add a Webhook node.
  2. Set the HTTP Method to POST and the Path to vapi-complete.
  3. Add a Set node to rename fields to match HubSpot's property names:
From To
caller_name firstname
company company
challenge description
  1. (Optional) Add a HubSpot node configured with your API key to Create Contact using the fields from the Set node.
  2. Add an HTTP Request node that calls Calendly's "Create Invitee" endpoint. Use the following JSON payload; replace YOUR_CALENDLY_TOKEN with the personal access token from Calendly's Integrations page.
{
 "method": "POST",
 "url": "https://api.calendly.com/scheduled_events",
 "headers": {
 "Authorization": "Bearer YOUR_CALENDLY_TOKEN",
 "Content-Type": "application/json"
 },
 "body": {
 "max_event_count": 1,
 "owner": "https://api.calendly.com/users/YOUR_USER_UUID",
 "invitees": [
 {
 "email": "{{ $json.email }}",
 "name": "{{ $json.firstname }}",
 "custom_questions": [
 {
 "question": "Company",
 "answer": "{{ $json.company }}"
 },
 {
 "question": "Challenge",
 "answer": "{{ $json.description }}"
 }
 ]
 }
 ]
 }
}
  1. Connect the HTTP Request node's output to a Respond to Webhook node that reads the invitee_uri from Calendly's response and speaks it back to the caller via Vapi's callback feature. In Vapi's dashboard, set Post-call webhook to point at the n8n Webhook node you just created (e.g., https://your-n8n-instance.com/webhook/vapi-return).

  2. Deploy the workflow and copy the public webhook URLs; paste them into the Vapi agent's on_complete and post-call webhook fields respectively.

5. Test the end-to-end flow

  1. Dial the Twilio number from any phone.
  2. Vapi should answer, ask the three qualification questions, and confirm that a calendar link will be sent.
  3. After the last answer, the n8n webhook triggers, creates a HubSpot contact (if enabled), and returns a Calendly scheduling URL.
  4. Vapi speaks the URL back (or you can have it send an SMS via Twilio for easier click-through).

If you hear the call drop or the conversation stops after the last question, check the Vapi agent logs and the n8n execution history for HTTP errors.

Where this breaks

Building a voice AI pipeline sounds linear, but several hidden constraints surface in production.

Rate limits - Vapi caps outbound webhook calls at 500 requests per hour on the free tier. If you expect more inbound traffic, upgrade or implement exponential back-off in the n8n HTTP Request node.

Auth token expiry - Both Vapi and Calendly use bearer tokens that rotate every 30 days. If a token expires, the webhook will return 401 Unauthorized and the workflow halts. Set up a Cron node in n8n that refreshes the Calendly token using the OAuth refresh endpoint, and store the fresh token in an Environment Variable.

Twilio call failures - If the Twilio number is not correctly linked to the Sip address (sip:[email protected]), the call will end with "Call failed". Double-check the AGENT_ID value and ensure the Sip domain is reachable (no firewall blocking port 5060).

Voice synthesis latency - Vapi's TTS can take up to 3 seconds per utterance on the free tier. If you chain many prompts, callers may perceive lag. Keep the dialogue under four turns, or pre-generate static prompts and serve them via the Play verb in Twiml.

CRM field mismatch - HubSpot expects specific property IDs; if the Set node's field names do not match, the contact creation fails silently. Verify the property keys in HubSpot's Custom Properties section and adjust the Set node mapping accordingly.

Warning: Ignoring webhook retry headers will cause lost lead data under high load. Configure n8n's Webhook node to respect the Retry-After header and enable Maximum Retries set to 5.

How does call routing work in Vapi?

Vapi uses SIP (Session Initiation Protocol) to accept inbound audio streams. When Twilio forwards a call to sip:[email protected], Vapi creates a media session that runs the LLM-driven script, captures speech-to-text in real time, and sends synthesized audio back over the same channel. The on_complete webhook is only triggered after the dialogue finishes or the caller hangs up. Understanding this flow helps you debug why a call might appear muted: the SIP handshake may have timed out if the Vapi agent is still initializing. In that case, restart the agent via the Vapi dashboard or re-POST the creation payload.

Which automation workflow connects voice AI, CRM, and scheduling?

The n8n workflow described in step 4 is the glue that turns raw voice data into actionable business objects. It follows a classic trigger → transform → action pattern:

  1. Trigger: Vapi webhook (vapi-complete).
  2. Transform: Set node renames slots to CRM field names.
  3. Action 1: HubSpot node creates or updates a contact.
  4. Action 2: HTTP Request node calls Calendly's Create Invitee API.
  5. Return: Respond node gives the caller a spoken link.

Because each node is a discrete, reusable component, you can swap HubSpot for Salesforce, or Calendly for Microsoft Bookings, without rewriting the entire pipeline. This modularity is what makes the automation workflow robust for scaling.

For a deeper technical reference, see n8n's documentation.

FAQ

How much does it cost to run this voice AI agent?

All the tools have free tiers that let you prototype end-to-end. Production usage (high call volume, advanced voice models, or premium Calendly features) may require paid plans. Check the Vapi, Twilio, and Calendly pricing pages for the latest rates.

What if I want to use a different CRM?

n8n supports dozens of CRM integrations out of the box. Replace the HubSpot node with the appropriate node (e.g., Salesforce, Pipedrive) and adjust the field mapping in the Set node to match the target CRM's schema.

Can I host Vapi on my own server to avoid cloud fees?

Vapi is a hosted SaaS product; there is no self-hosted edition. If you need on-premise control, you would have to replace Vapi with an open-source stack such as Mozilla DeepSpeech + Coqui TTS, but that adds significant engineering overhead.

How do I handle international callers?

Twilio supplies phone numbers in many countries. Purchase the appropriate national number, and update the Voice → A CALL COMES IN webhook URL to point at the same n8n endpoint. Vapi's TTS supports dozens of locales; set the voice field in the agent payload to the appropriate language code (e.g., en-GB-Standard-A for UK English).

Where can I learn more about building AI-powered phone agents?

Our free guide walks you through every API call, includes sample n8n workflows, and shows how to monetize the solution: https://getaab.com/free. For ideas on packaged products you can sell, see our curated list of AI automations you can sell: https://getaab.com/ai-automations-to-sell.

By following these steps you now have a fully functional voice AI agent that answers inbound calls, qualifies leads, and books meetings without any manual intervention. The same pattern can be duplicated for support hotlines, appointment reminders, or any scenario where spoken interaction needs to be automated at scale. Happy building.

The best free AI models 2026 for an automation-first business

2026-08-22 08:29:44

The best free AI models 2026 are the ones that give you production-grade quality without a bill at the end of the month. In practice that means using Groq's ultra-low-latency mix, Google Gemini's 1 M-token free quota, Meta's LLaMA 2 (self-hosted), DeepSeek's open-source v2.5, and Mistral-7B-Base on a free cloud tier. Hook them up to an automation platform like n8n and you can run a full SaaS pipeline - lead scoring, email drafting, image captioning, or ticket routing - without paying for inference.

Below you'll find the exact stack, a step-by-step build, the gotchas that usually bite newcomers, and a short FAQ so you can get the best free AI models 2026 live in under two hours.

What you need

Tool / Model Plan / Price (as of 2026) Role in the pipeline
Groq (Mixtral-8x7B-instruct) Free tier: 200 k tokens / month, no credit-card required (see Groq pricing) Low-latency text generation for chat & summarisation
Google Gemini 1.5 Flash Free tier: 1 M input tokens / month, 0.5 M output tokens / month (check Google Cloud AI) Multi-modal (text + image) support, best for classification and translation
Meta LLaMA 2 13B Self-hosted Docker (CPU) - $0, or hosted on Runpod free credits (up to $5) Deep-knowledge base Q&A, fine-tuning on proprietary data
DeepSeek-V2.5 Free tier on DeepSeek API: 150 k tokens / month (no card) Creative writing, code suggestions
Mistral-7B-Base Free tier on Mistral Cloud: 100 k tokens / month (requires OAuth) Structured data extraction, function calling
n8n (automation) Community Edition (self-hosted Docker) - free Orchestrates API calls, branching, retries
Docker Desktop Free for personal use Container runtime for LLaMA 2
Node.js 18+ Free (runtime) Needed for custom JS functions inside n8n

Estimated build time: 90 minutes for a fresh machine (install Docker, pull LLaMA, configure n8n) plus 30 minutes of testing. Total ~2 hours.

Building a production-grade automation pipeline with the best free AI models 2026

Below is a concrete example: an inbound-lead workflow that (1) scores the lead with Groq, (2) classifies language with Gemini, (3) enriches with a LLaMA-2 knowledge-base lookup, and (4) writes a personalized email using DeepSeek. All steps run on free tiers, so you stay under the combined ~650 k token limit per month.

1. Spin up the n8n Community Edition

docker run -d \
 --name n8n \
 -p 5678:5678 \
 -e N8N_BASIC_AUTH_ACTIVE=true \
 -e N8N_BASIC_AUTH_USER=admin \
 -e N8N_BASIC_AUTH_PASSWORD=changeme \
 n8nio/n8n:latest

What this does: launches n8n on http://localhost:5678 with basic auth. Adjust the password immediately.

2. Add API credentials as Global Variables

Variable Value (example) Where to set
GROQ_API_KEY gsk_XXXXXXXXXXXXXXXX n8n → Settings → Environment Variables
GEMINI_API_KEY AIzaSy... same
DEEPSEEK_API_KEY ds_XXXXXXXXXXXXXXXX same
MISTRAL_API_KEY msk_XXXXXXXXXXXXXXXX same

All five free tiers together give over 650 k tokens per month of inference without any charge.

3. Pull and run LLaMA 2 13B locally (CPU-only, ~12 GB VRAM)

docker pull ghcr.io/abetlen/llama-cpp:latest
docker run -d --name llama2 \
 -p 8080:8080 \
 -e MODEL_PATH=/models/llama-2-13b-chat.ggmlv3.q8_0.bin \
 -v $HOME/llama-models:/models \
 ghcr.io/abetlen/llama-cpp:latest \
 --model /models/llama-2-13b-chat.ggmlv3.q8_0.bin \
 --host 0.0.0.0 --port 8080

What this does: spins up a lightweight REST endpoint (http://localhost:8080/completions) that n8n can call just like an external API. The model file is ~12 GB; download it from Meta's official repository (requires free sign-up).

4. Create the "Score Lead with Groq" HTTP Request node

  • Method: POST
  • URL: https://api.groq.com/openai/v1/chat/completions
  • Headers:
    • Authorization: Bearer {{ $env.GROQ_API_KEY }}
    • Content-Type: application/json
  • Body (JSON):
{
 "model": "mixtral-8x7b-instruct",
 "messages": [
 {"role": "system", "content": "You are a lead-scoring assistant. Return a score 0-100 and a short rationale."},
 {"role": "user", "content": "{{$json[\"lead_text\"]}}"}
 ],
 "temperature": 0.2,
 "max_tokens": 150
}

What this does: sends the raw inbound lead text to Groq's Mixtral-8x7B and gets back a JSON with a numeric score and rationale.

5. Add a "Detect Language with Gemini" node (Google Cloud Functions)

  • Method: POST
  • URL: https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={{ $env.GEMINI_API_KEY }}
  • Body:
{
 "contents": [
 {"role": "user", "parts": [{"text": "{{$json[\"lead_text\"]}}"}]}
 ],
 "generationConfig": {"temperature": 0.0, "maxOutputTokens": 50},
 "systemInstruction": {"parts": [{"text": "Identify the language of the input text and output the ISO-639-1 code."}]}
}

What this does: yields a two-letter language code (e.g., en, es) that later branches the workflow.

6. Branch on language using "If" node

If {{ $json.language == "en" }} → continue; else route to a DeepSeek translation step (not shown) because the free tier for Gemini only covers English-centric prompts well.

7. Enrich with LLaMA 2 knowledge base

Add an HTTP Request node pointing at your local LLaMA service:

  • URL: http://localhost:8080/completions
  • Body:
{
 "prompt": "Answer the question based on the company knowledge base:\n\nQ: {{$json.lead_question}}\nA:",
 "max_tokens": 200,
 "temperature": 0.3,
 "stop": ["\n"]
}

What this does: queries the self-hosted LLaMA 2 for a contextual answer, using the free compute you already have.

8. Draft a personalized email with DeepSeek-V2.5

  • URL: https://api.deepseek.com/v1/chat/completions
  • Headers: same pattern, Authorization: Bearer {{ $env.DEEPSEEK_API_KEY }}
  • Body:
{
 "model": "deepseek-v2.5",
 "messages": [
 {"role": "system", "content": "You are a sales copywriter. Write a 3-sentence email that references the lead's industry and includes a call-to-action."},
 {"role": "user", "content": "Lead score: {{$node['Score Lead with Groq'].json.score}}\nIndustry: {{$json.industry}}\nEnriched answer: {{$node['LLaMA Enrich'].json.answer}}"}
 ],
 "temperature": 0.7,
 "max_tokens": 250
}

What this does: produces a ready-to-send email body that you can hand off to an SMTP node or a Gmail node.

9. Send the email (SMTP node)

Configure n8n's built-in SMTP node with your provider's credentials (e.g., Gmail's App Password). Map Subject, To, and HTML fields from the DeepSeek output.

10. Log the whole interaction to a Google Sheet (optional)

Add a Google Sheets node (free tier: 500 writes/day) and write the lead ID, score, language, and email status. This gives you an audit trail for future model-fine-tuning.

You now have an end-to-end, production-grade automation that runs entirely on the **best free AI models 2026.** The whole workflow lives inside a single n8n canvas, can be duplicated for other use-cases (ticket triage, content generation), and respects each provider's free quota.

Where this breaks

Failure mode Why it happens Mitigation
Token exhaustion Combined free quotas (~650 k tokens) are easy to exceed on high-volume SaaS (10 k leads/month ≈ 650 k tokens). Implement a token-budget node that checks $env.GROQ_USAGE (track via webhook) and falls back to a cheaper model (Mistral) when close to limit.
Rate-limit errors Groq caps at 60 req/s; Gemini at 10 req/s for free tier. Add a Sleep node (e.g., 200 ms) between calls, or use n8n's built-in Concurrency limiter (maxConcurrency: 5).
Auth expiry API keys for cloud providers rotate after 90 days if not tied to a billing account. Store keys in n8n Credentials with auto-refresh hooks where supported (Google OAuth). Schedule a Cron node to ping each provider's "token-info" endpoint weekly.
Self-hosted LLaMA GPU vs CPU mismatch The Docker image defaults to CPU; loading the 13 B model on a laptop can take >5 min, causing timeouts. Set the HTTP Request node's Timeout to 120 s, and pre-warm the container during off-hours. For higher throughput, attach a cheap GPU VM (e.g., AWS g4dn.xlarge) and switch the endpoint URL.
Unexpected response shape Different providers return choices[0].message.content vs choices[0].text. Use n8n's Set node with JSONPath expressions that adapt per model, or wrap each HTTP request in a Function node that normalises the output.
Cost blowup from hidden usage Some free tiers charge for "input tokens" only; you might think only outputs count. Monitor the Billing dashboard of each provider weekly. Add a n8n Webhook that fires on the provider's usage alert email (most send a webhook on >80 % quota).

For a deeper technical reference, see n8n's documentation.

FAQ

Which free model gives the fastest latency for chat?

Groq's Mixtral-8x7B-instruct runs on dedicated inference hardware and typically returns a response in ≈120 ms for ≤200-token prompts. Gemini is slightly slower (≈250 ms) but offers multi-modal support.

Can I use LLaMA 2 for commercial purposes without paying?

Yes - Meta's LLaMA 2 Community License permits commercial use as long as you do not redistribute the model weights. Running it on your own hardware (or on a free-credit cloud VM) complies with the license.

What happens if my free-tier token budget is exhausted mid-workflow?

Design the workflow to catch 429 errors (rate-limit) and branch to a "fallback" model like Mistral-7B-Base, which still provides acceptable quality at a lower token cost. You can also queue the request for the next day using n8n's Delay node.

Are there any hidden limits on the DeepSeek free tier?

DeepSeek caps 150 k tokens/month and enforces a per-minute request limit of 30 rpm. Exceeding either results in a 429 Too Many Requests response. Monitor usage with a simple HTTP Request to https://api.deepseek.com/v1/usage.

How do I keep my API keys secure in n8n?

Never hard-code keys in node JSON. Instead, add them under Settings → Environment Variables or use n8n's Credentials store, which encrypts values at rest. Rotate keys at least every 90 days.

Is the "best free AI models 2026" list stable for the next year?

Free-tier offerings are subject to change. Bookmark the providers' pricing pages (Groq, Google Cloud AI, DeepSeek, Mistral) and schedule a quarterly review of your token usage. The core set - Groq, Gemini, LLaMA 2, DeepSeek, Mistral - has been consistent for the past 18 months, making it a safe foundation for most automation businesses.

Ready to try the stack? Grab the free resources, spin up the Docker containers, and start building your own workflows. For more hands-on guidance, check out the tool comparison and dive into the Vault for pre-made n8n templates. And if you need a curated list of free AI APIs with your own usage dashboard, claim your free AAB account now.

ai agents vs automations: When to build an autonomous agent and when a simple workflow suffices

2026-08-22 08:29:16

What's the difference? An AI agent is a loop-driven system that can decide which tool to call next, keep state across interactions, and adapt its behaviour. An automation is a fixed sequence of steps that runs the same way every time. In this guide you'll build both a plain n8n workflow that sends a prompt to OpenAI and stores the answer, and a full RAG-enabled AI agent that decides when to fetch documents, when to query the LLM, and when to respond. By the end you'll see why most teams over-engineer, and you'll have a production-ready example you can ship tomorrow.

Key insight: If your use-case requires conditional tool use, memory, or dynamic goal-setting, you need an AI agent; otherwise a straight automation is cheaper, faster, and easier to maintain.

What you need

Tool Plan / Price Role
n8n (open-source workflow engine) Community edition (self-hosted, free) - see https://n8n.io/pricing for hosted options Orchestrates both automation and agent pipelines
OpenAI API (ChatGPT/GPT-4) Pay-as-you-go - see https://openai.com/api/pricing Generates natural-language responses
Pinecone (vector store) Free tier or paid plan - see https://www.pinecone.io/pricing Holds document embeddings for RAG
Docker (container runtime) Free Runs n8n locally or in CI
Git (version control) Free Stores workflow definitions

Estimated build time: ~4 hours for a complete agent (including embedding documents) and ~1 hour for the plain automation.

Step-by-step build

1. Set up n8n locally

# Pull the official n8n Docker image and start it on port 5678
docker run -d --name n8n \
 -p 5678:5678 \
 -e N8N_BASIC_AUTH_ACTIVE=true \
 -e N8N_BASIC_AUTH_USER=admin \
 -e N8N_BASIC_AUTH_PASSWORD=secret \
 n8nio/n8n

What this does: launches a self-hosted n8n instance with basic auth. After a few seconds open http://localhost:5678 and log in with the credentials above.

2. Create the plain automation workflow

  1. In the n8n UI, click New Workflow.
  2. Add a Webhook node (trigger URL: /automation). This receives a JSON payload { "prompt": "Your question?" }.
  3. Connect the Webhook to an OpenAI node (provided by n8n).
    • Model: gpt-4o-mini (or whichever you have access to).
    • Prompt: {{$json["prompt"]}}.
  4. Add a Set node to format the LLM output: response = {{$node["OpenAI"].json["choices"][0]["message"]["content"]}}.
  5. End with a Respond node that returns { "answer": {{$json["response"]}} }.

Export the workflow JSON so you can version-control it:

{
 "nodes": [
 {
 "name": "Webhook",
 "type": "n8n-nodes-base.webhook",
 "parameters": {
 "path": "automation",
 "httpMethod": "POST"
 }
 },
 {
 "name": "OpenAI",
 "type": "n8n-nodes-base.openAi",
 "parameters": {
 "operation": "chatCompletion",
 "model": "gpt-4o-mini",
 "messages": [
 {
 "role": "user",
 "content": "{{$json[\"prompt\"]}}"
 }
 ]
 }
 },
 {
 "name": "Set",
 "type": "n8n-nodes-base.set",
 "parameters": {
 "values": {
 "response": "={{$node[\"OpenAI\"].json[\"choices\"][0][\"message\"][\"content\"]}}"
 }
 }
 },
 {
 "name": "Respond",
 "type": "n8n-nodes-base.respond",
 "parameters": {
 "responseData": "={{$json}}"
 }
 }
 ],
 "connections": {
 "Webhook": {
 "main": [
 [
 {
 "node": "OpenAI",
 "type": "main",
 "index": 0
 }
 ]
 ]
 },
 "OpenAI": {
 "main": [
 [
 {
 "node": "Set",
 "type": "main",
 "index": 0
 }
 ]
 ]
 },
 "Set": {
 "main": [
 [
 {
 "node": "Respond",
 "type": "main",
 "index": 0
 }
 ]
 ]
 }
 }
}

What this does: the JSON defines a linear pipeline - receive a prompt, send it to the LLM, wrap the response, and return it. There is no conditional logic or memory; each request is isolated.

3. Prepare document embeddings for RAG

# Install the official OpenAI Python client
pip install openai tqdm

# Encode a folder of .txt files into vectors and upsert them into Pinecone
python - <<'PY'
import os, openai, pinecone, tqdm

openai.api_key = os.getenv("OPENAI_API_KEY")
pinecone.init(api_key=os.getenv("PINECONE_API_KEY"), environment="us-west1-gcp")

index = pinecone.Index("rag-demo")
folder = "docs"
for filename in tqdm.tqdm(os.listdir(folder)):
 if not filename.endswith(".txt"):
 continue
 with open(os.path.join(folder, filename), "r") as f:
 text = f.read()
 # Create a single embedding for the whole doc (replace with chunking for large files)
 resp = openai.Embedding.create(model="text-embedding-3-large", input=text)
 vector = resp["data"][0]["embedding"]
 index.upsert(vectors=[(filename, vector, {"text": text})])
print("All docs indexed")
PY

What this does: reads each .txt file, generates an embedding with OpenAI's text-embedding-3-large model, and stores the vector in Pinecone. The script uses environment variables for API keys - store them securely (e.g., in a .env file).

4. Build the AI agent workflow

  1. Create a new workflow called RAG Agent.
  2. Add a Webhook node (trigger URL: /agent). Input payload: { "question": "How does X work?" }.
  3. Add a Function node named DecideAction. Its JavaScript decides whether a document lookup is needed:
// Very simple heuristic: if the prompt contains the word "explain", fetch docs
const prompt = $json["question"];
if (prompt.toLowerCase().includes("explain")) {
 return [{ action: "retrieval", query: prompt }];
}
return [{ action: "direct", query: prompt }];
  1. Connect DecideAction to a Switch node that branches on action.
  • Branch "retrieval": a. Pinecone Search node (n8n has a community Pinecone node; if not, use an HTTP Request node).
  • Namespace: rag-demo.
  • Query vector: compute on-the-fly using OpenAI's embedding endpoint (text-embedding-3-large).
  • Top K: 3.
    b. Merge node to concatenate retrieved text fields.
    c. Feed the concatenated context and original question to an OpenAI node (prompt: Context: {{ $json["context"] }}\nQuestion: {{ $json["question"] }}) and return the answer.

  • Branch "direct":
    a. Send the original question straight to an OpenAI node (same model, no context).

  1. Close each branch with a Respond node that returns { "answer": ... }.

Export the workflow; the JSON will be larger because of the conditional logic, but the core principle is the same: the agent retains state (action) and decides which tool to call next.

5. Test both endpoints

# Test automation (fixed pipeline)
curl -X POST http://localhost:5678/webhook/automation \
 -H "Content-Type: application/json" \
 -d '{"prompt":"What is the capital of France?"}'

# Test agent (dynamic pipeline)
curl -X POST http://localhost:5678/webhook/agent \
 -H "Content-Type: application/json" \
 -d '{"question":"Explain the difference between supervised and unsupervised learning."}'

What you should see: the automation returns a single sentence answer; the agent may include relevant excerpts from your indexed docs before the LLM's answer, demonstrating true tool use.

6. Deploy (optional)

If you prefer a managed n8n instance, sign up at https://n8n.io and import the JSON files via the UI. For production you'll also want to:

  • Enable HTTPS with a reverse proxy (e.g., Nginx).
  • Store API keys in environment variables (OPENAI_API_KEY, PINECONE_API_KEY).
  • Set rate limits on the webhook nodes to protect against abuse.

You can now sell these automations as part of a service offering - see the catalog at https://getaab.com/ai-automations-to-sell for ready-made ideas.

Where this breaks

Failure mode Symptom Fix
OpenAI rate-limit 429 Too Many Requests from the OpenAI node Back-off with exponential delay; consider batching requests or upgrading your OpenAI quota (see the pricing page).
Pinecone vector limit Upsert error or missing results Verify your current plan's vector quota; prune old vectors or migrate to a higher tier (check Pinecone's pricing).
n8n authentication lapse Webhook returns 401 Unauthorized Refresh the basic auth password in the Docker environment or switch to OAuth if you move to the hosted service.
Embedding latency Long delay before the agent can query Pinecone Cache embeddings locally or pre-compute them offline; avoid generating an embedding on each request.
Branching logic error Agent always takes the "direct" path even for retrieval queries Ensure the DecideAction function correctly parses the incoming JSON; check $json["question"] naming.
Cost surprise Monthly bill spikes due to high LLM usage Add a usage monitor (n8n's built-in analytics or external logging) and set hard caps on token count per request.

For a deeper technical reference, see n8n's documentation.

FAQ

What is an AI agent?

An AI agent is a system that loops: it receives input, decides which tool (LLM, database, API) to invoke, possibly updates an internal state, and repeats until a goal is satisfied.

When should I choose a plain automation over an agent?

Pick a plain automation when the process is deterministic - no branching, no need to fetch external knowledge, and no requirement to remember prior steps. It's cheaper, faster to develop, and easier to debug.

How does the RAG component fit into an AI agent?

RAG (Retrieval-Augmented Generation) supplies external context to the LLM. In the agent example, the decision node routes the question to a Pinecone search, merges retrieved texts, and feeds them into the LLM, enabling factual answers that go beyond the model's internal knowledge.

Can I run this stack entirely self-hosted?

Yes. All components - n8n, OpenAI client, and Pinecone (via its managed service) - can be run from Docker with environment variables for keys. The only cloud-hosted piece is the OpenAI API, which you must access via the internet.

How do I monitor usage to avoid surprise bills?

Use n8n's Execution Statistics panel, or export logs to a monitoring service (e.g., Datadog). Track two metrics: LLM token count per request and Pinecone query volume. Set alerts when thresholds approach your plan limits.

Where can I find more ready-made automations?

Explore the curated list at https://getaab.com/ai-automations-to-sell and the detailed RAG example in the vault at https://getaab.com/vault/support-agent-rag.

If you're ready to ship a robust AI-powered solution, start with the simple automation, then evolve it into an agent when you hit the "needs tool use" wall. The distinction between ai agents vs automations isn't academic - it's the difference between a one-off script and a scalable, maintainable product.

Get started for free: https://getaab.com/free