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.
| Tool | Plan / Price | Role |
|---|---|---|
| n8n (open-source workflow engine) | ||
| Community edition (self-hosted, free) - see | ||
Estimated build time: ~4 hours for a complete agent (including embedding documents) and ~1 hour for the plain automation.
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.
/automation
). This receives a JSON payload { "prompt": "Your question?" }
. gpt-4o-mini
(or whichever you have access to). {{$json["prompt"]}}
. response = {{$node["OpenAI"].json["choices"][0]["message"]["content"]}}
. { "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.
pip install openai tqdm
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()
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).
/agent
). Input payload: { "question": "How does X work?" }
.
// 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 }];
action
. rag-demo
. 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).
{ "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.
curl -X POST http://localhost:5678/webhook/automation \
-H "Content-Type: application/json" \
-d '{"prompt":"What is the capital of France?"}'
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.
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:
OPENAI_API_KEY
, PINECONE_API_KEY
). 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.
| 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.
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.
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.
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.
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.
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.
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