Files
agents/n8n-Bale-file-fetch/HOW_IT_WORKS.md
2026-08-29 18:37:48 +03:30

4.0 KiB

Bale File Agent — How It Works

The idea

The interviewer's brief was: type a natural-language request like "grab the devops PDFs from this channel" and have an agent go fetch them — the way a person would, but automated. This workflow implements that as a short pipeline in n8n, using an LLM only for the one step that actually needs language understanding (turning a sentence into a structured filter), and plain code/API calls for everything else.

The pipeline, step by step

Chat message
   ↓
LLM parses intent → {file_type, keyword}
   ↓
Fetch messages the Bale bot has seen
   ↓
Filter messages against the parsed intent
   ↓
Download each match from Bale
   ↓
Save to disk

1. Chat Trigger

A live chat box inside the n8n editor. Whatever you type becomes the field chatInput on the item that flows to the next node. This stands in for "a person typing a request" — in a real deployment this could be a Slack bot, a web form, or Bale itself.

2. Parse command (LLM)

A single HTTP call to BazaarLink's OpenAI-compatible endpoint. The system prompt forces the model into a narrow, mechanical job: read the sentence, output nothing but {"file_type": "...", "keyword": "..."}. This is intentionally the only place an LLM is used — everything else is deterministic, which keeps the workflow fast, cheap, and easy to debug.

3. Extract filters

A small Code node that takes the LLM's raw text response, strips any accidental markdown fences, and parses it into real JSON so the rest of the workflow can use .file_type and .keyword as normal fields.

4. Fetch Bale updates

Calls Bale's Bot API (getUpdates) — a long-polling endpoint that returns every message the bot has seen since it was last called. Bale's bot API is essentially a clone of Telegram's, so this behaves the same way: it returns new activity, not full channel history.

5. Filter matches

Another Code node. Loops through the fetched messages, and for each one:

  • Checks whether it's a PDF document or a photo (matching file_type)
  • Checks whether the caption or filename contains the requested keyword
  • If both match, records the file's file_id and real file_name for the next steps

This is where "understanding the request" turns into "acting on the request" — the filtering logic itself needs no AI, just the structured output the LLM already produced.

6. Get file path → Download file

Two calls to Bale's API mirroring Telegram's own two-step file download: getFile resolves a file_id into a temporary file_path, then a second request downloads the actual bytes from that path.

7. Save to disk

Writes the downloaded binary to a folder on the n8n host, using the real original filename (not Bale's internal file token) so the output is something a human would recognize.

Why it's built this way

  • LLM only where it's needed. Intent parsing is genuinely a language task; filtering, fetching, and downloading are not. Using an LLM for the whole thing would be slower, more expensive, and harder to debug than necessary.
  • Deterministic filtering. The match logic is plain code, so it's predictable and cheap to test — you can reason about exactly why a file did or didn't match.
  • Small, inspectable steps. Each node does one clear thing, so a failure (like a bad file path, or an expired long-poll) is easy to isolate — you can see exactly which stage produced bad data by clicking through node outputs in n8n's UI.

What a production version would add

  • A persistent index of channel content (log every incoming file to a small database or sheet as it arrives via webhook), so requests aren't limited to whatever getUpdates happens to still be holding.
  • Retry logic on the LLM call for transient network blips.
  • A proper credential store in n8n instead of hardcoded tokens/keys.
  • Possibly a semantic match (embeddings or a second LLM pass) instead of plain substring matching on captions, so "grab the devops stuff" also catches a file captioned "infrastructure automation" even without the literal word "devops."