DI praktikoje · 1 dalis
Levels of working with AI
From a chat window to autonomous agents: ten levels of interacting with AI, what each one needs, with code for the API, tool use and agent loops, and how to choose the right level.
The ladder at a glance
Most confusion about agents comes from mixing levels. Using Copilot in a notebook, calling a model from a script and running an unattended agent are three different things, with different setup, cost and risk. Here are ten levels, grouped into four tiers.
| Tier | Level | What you do | Who runs the loop | Data engineering example |
|---|---|---|---|---|
| Use | 1. Chat window | Paste a question, read the answer | You | Explain a Spark stack trace |
| 2. Assistant in your tool | Ask inside the notebook or editor, which shares its context | You | Copilot in a Fabric notebook, Genie Code in Databricks | |
| 3. Grounded chat | Give it standing context: instructions, files, retrieved documents | You | A project holding your naming rules and schema docs | |
| Configure | 4. Configured agent | Choose the data, write instructions and examples. The platform runs the agent | The platform | A Genie space, a Fabric data agent |
| Build | 5. API call | A script calls a model with a key | Your code, one call | Write descriptions for 400 columns |
| 6. Tool use | The model asks for a function, your code runs it | Your code, one step | Answer “how many rows?” by calling a function | |
| 7. Agent loop | The model plans and repeats tool calls until the task is done | Your code or a harness | A coding agent fixing a failing pipeline | |
| Scale | 8. Shared tools | Package tools and know-how once (MCP, skills) for many agents | A protocol | Managed MCP servers, Fabric MCP server, agent skills |
| 9. Multi-agent | An orchestrator routes work to specialist agents | The orchestrator | A supervisor choosing between a SQL agent and a docs agent | |
| 10. Autonomous | Runs on a schedule or an event with nobody watching | A scheduler or platform | Failure triage that opens a ticket overnight |
Two more things sit outside the ladder and apply at every level: the model you pick (a small fast model for bulk work, a large one for planning) and evaluation, which gets its own section below.
Tier 1: Use
Level 1: the chat window
You copy code or an error in, read the answer, and copy the result back. It needs no setup and it is where almost everyone starts.
Good for: explaining unfamiliar code, drafting a regex or a SQL window function, understanding an error. The model sees only what you paste.
Watch for: never paste secrets, credentials or customer data. Check your organisation’s rules and the tool’s data-handling terms first.
Level 2: an assistant inside your tool
The assistant lives in the notebook, SQL editor or pipeline designer and sees its context: attached tables, schemas, the code in front of you. That context is why it is more accurate than a chat window. Examples are Copilot in Fabric notebooks and Data Factory, and Genie Code in Databricks. Parts 2 and 3 cover them in detail.
You are still the loop. You read each suggestion and decide.
Level 3: grounded chat
Here you give the model standing context so you stop repeating yourself: a project or custom instructions with your conventions (“bronze tables are append-only, silver uses merge”), attached files such as the data dictionary, or documents retrieved on demand.
For a data team the highest-value context is small and boring: naming standards, the medallion layer rules, how a metric is defined, and a list of tables people should not use.
Tier 2: Configure
Level 4: a configured agent
Now the platform runs the agent loop and you configure it. You pick the data it can use, write instructions, and add example questions with the right SQL. Two examples:
- Genie spaces in Databricks.
- Fabric data agents in Microsoft Fabric.
This is the right level for letting analysts ask questions of governed data, because it needs no code, runs with the asking user’s permissions, and the platform handles the plumbing. The limit is flexibility: you shape it with instructions and examples, not with logic. Parts 2 and 3 show how to configure each one.
Tier 3: Build
At this tier you write code. The three levels build directly on each other, so it is worth seeing all three.
Level 5: an API call with a key
You call the model from a script or notebook. You need an API key (or platform credentials), you pay per token, and each call stands alone.
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
columns = ["cust_no", "ord_dt", "net_rev_chf"]
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Write a one-line business description for each column of a "
f"sales fact table: {columns}. Reply as JSON {{column: description}}.",
}],
)
print(next(b.text for b in response.content if b.type == "text"))
This is still not an agent. The model answers once, and your code decides what happens next. It is the right level for bulk, repeatable text work: documentation for hundreds of columns, classifying free-text fields, summarising run logs.
Handling keys. Keep them out of notebooks and repositories. Use environment variables locally and the platform’s secret store in the cloud:
# Databricks notebook
api_key = dbutils.secrets.get(scope="ai", key="anthropic-api-key")
# Fabric notebook
api_key = notebookutils.credentials.getSecret("https://<your-vault>.vault.azure.net/", "anthropic-api-key")
client = anthropic.Anthropic(api_key=api_key)
The same ideas apply to other providers’ APIs, such as Azure OpenAI. Only the SDK calls differ. For large batches, look at the provider’s batch endpoint, which is cheaper for work that is not urgent. For machine-readable output, use structured outputs instead of asking nicely for JSON.
Level 6: tool use (functions)
Now you describe your functions to the model. It cannot run them. It answers with a request: “call row_count with this table”. Your code runs the function and returns the result. This is the step that connects a model to your data without giving it direct access.
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-opus-5-5"
ALLOWED_TABLES = {"gold.sales.fct_orders", "gold.sales.dim_customer"}
def row_count(table: str) -> str:
if table not in ALLOWED_TABLES: # the model proposes, your code decides
return f"Table {table} is not allowed."
# run SELECT COUNT(*) on your SQL warehouse here; stubbed for the example
return "1250431"
tools = [{
"name": "row_count",
"description": "Return the number of rows in a table. Read-only.",
"input_schema": {
"type": "object",
"properties": {
"table": {"type": "string", "description": "Fully qualified name, e.g. gold.sales.fct_orders"}
},
"required": ["table"],
},
}]
messages = [{"role": "user", "content": "How many rows are in gold.sales.fct_orders?"}]
response = client.messages.create(model=MODEL, max_tokens=1024, tools=tools, messages=messages)
if response.stop_reason == "tool_use":
call = next(b for b in response.content if b.type == "tool_use")
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": call.id, "content": row_count(**call.input)},
]})
response = client.messages.create(model=MODEL, max_tokens=1024, tools=tools, messages=messages)
print(next(b.text for b in response.content if b.type == "text"))
Three design rules show up already. Validate every input the model sends (here, an allow-list of tables). Give each function one narrow job and a precise description. Make the dangerous ones impossible, not just discouraged: read-only connections, not “please do not write”.
Level 7: the agent loop
Wrap level 6 in a loop and you have an agent. The model calls a tool, reads the result, decides the next step, and repeats until it is done. What separates a useful agent from a runaway one is the stop conditions and the gates you put around the loop:
MAX_STEPS = 8
NEEDS_APPROVAL = {"run_sql_write", "deploy_pipeline"}
for step in range(MAX_STEPS):
response = client.messages.create(model=MODEL, max_tokens=4096, tools=tools, messages=messages)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break # finished, or stopped for another reason: check response.stop_reason
results = []
for call in (b for b in response.content if b.type == "tool_use"):
if call.name in NEEDS_APPROVAL and input(f"Run {call.name}({call.input})? [y/N] ") != "y":
output, is_error = "Declined by the user.", True
else:
output, is_error = run_tool(call.name, call.input), False # your dispatcher
results.append({"type": "tool_result", "tool_use_id": call.id, "content": output, "is_error": is_error})
messages.append({"role": "user", "content": results}) # all results in one message
else:
print("Stopped: step limit reached")
Four things make this safe enough to use: a step limit, an approval gate on anything that changes state, errors returned to the model as results (so it can recover), and a full log of the messages. The SDKs also offer helpers that run this loop for you, but writing it once by hand shows what the products below are doing.
Coding agents such as Claude Code, GitHub Copilot’s agent mode and Genie Code are this loop, already built, with file and shell tools and an approval system. Most engineers use level 7 through a product, not by writing the loop.
Tier 4: Scale
Level 8: shared tools with MCP and skills
When several agents need the same tools, defining them separately in each is wasteful and unsafe. The Model Context Protocol (MCP) packages tools once, as a server, so any compatible client can use them. Databricks provides managed MCP servers, Microsoft provides a Fabric MCP server, and dbt provides an MCP server for project metadata.
Skills are the other half. A skill is a written instruction file that teaches an agent how to do a kind of work in your platform. Databricks puts it this way: skills teach the agent how to work; MCP servers let it do the work. Parts 2 and 3 show the real servers and install commands.
What changes at this level is governance: one place to control which tools exist, who can call them, and what is logged.
Level 9: multi-agent
An orchestrator receives the request and routes it to specialist agents: one that knows sales data, one that searches documentation, one that creates tickets. Both platforms support this. A Databricks supervisor agent can orchestrate Genie agents, agent endpoints, Unity Catalog functions and MCP servers. A published Fabric data agent can be called by external orchestrators such as Copilot Studio and Azure AI Foundry.
Use it when one agent’s tool list has grown so long that it picks wrongly, or when different domains have different owners and permissions. Do not start here. Every added agent multiplies what you have to test.
Level 10: autonomous agents in production
The agent runs on a schedule or on an event with nobody watching, for example triaging every failed pipeline run overnight. This is where all the earlier concerns become requirements:
- a narrow scope and a dedicated identity with the least privilege that works,
- budgets for steps, tokens and cost, with a hard stop,
- a sandbox: no production writes, only recommendations and tickets at first,
- an evaluation set that runs on every change,
- logging and alerting on what the agent did and why.
Fabric’s operations agent for pipelines, covered in Part 2, is an example of this level as a product: it watches pipeline health and sends insights to Teams.
Two things that matter at every level
Context is the real lever
A model can only use what it can read. A star schema with clear names, documented columns and one definition per metric is the best prompt you will ever write, and it helps every human reader too. Put the rules into the platform, not into a chat window:
-- Databricks SQL: descriptions live in Unity Catalog, so every tool and agent sees them
COMMENT ON TABLE gold.sales.fct_orders IS
'One row per order line. Grain: order_id + line_no. Revenue is net of discounts and excludes VAT.';
ALTER TABLE gold.sales.fct_orders ALTER COLUMN net_revenue
COMMENT 'Net revenue in CHF after discounts, excluding VAT. Use this for all revenue questions.';
Three habits pay off on both platforms:
- Name the grain of every fact table in its description.
- Precompute awkward logic. An
is_last_complete_monthflag in the date dimension is safer than hoping an agent writes the right date arithmetic every time. - Define each metric once, in a semantic model, a metric layer or a documented view, and tell the agent to use it.
Evaluate like you test pipelines
Anything that answers questions or changes code needs a test suite. Keep a small file of golden questions beside the configuration:
- question: Net revenue by product category last month
expected_sql: |
SELECT p.category, SUM(f.net_revenue) FROM ...
must_match: result_set # compare results, not SQL text
- question: How many active customers do we have in Switzerland?
expected_sql: |
SELECT COUNT(*) FROM dim_customer WHERE country = 'CH' AND is_active
must_match: result_set
Run every question after each change to instructions, examples or schema, and compare the result sets, not the SQL text, because many different queries are correct. When a real user finds a wrong answer, add it to the file before fixing it.
Choosing the level
| The job looks like… | Use level | Why |
|---|---|---|
| A one-off question or explanation | 1 | No setup, no risk |
| Writing code inside a notebook or editor | 2 | Context comes for free |
| Applying the same conventions repeatedly | 3 | Standing context saves effort |
| Analysts asking questions of governed data | 4 | Platform-run, permission-aware, no code |
| The same transformation on hundreds of items | 5 | Cheap, repeatable, easy to test |
| An assistant that needs live data or system access | 6 | Your functions are the safety boundary |
| An open-ended task with several steps | 7 | The model must adapt as it learns |
| Many tools or many assistants | 8 | Define and govern once |
| Several domains with separate owners | 9 | Split by responsibility |
| Work that must happen with nobody watching | 10 | Only with budgets, a sandbox and evaluation |
Move up a level only when the lower one has clearly run out: you need repeatability (3, 4), volume (5), live data (6), adaptability (7), reuse (8), separation (9) or unattended operation (10).
Controls grow with the level
The higher you go, the more of these you need. Treat them as a checklist:
- Start read-only. Allow writes only in development and only with approval.
- One identity per agent with the least privilege that works. No shared admin tokens.
- Keep secrets out of prompts, instructions, notebooks and repositories.
- Review every generated diff. Treat the agent like a fast, confident junior engineer.
- Treat what an agent reads as untrusted. Tickets, documents and table comments can contain instructions aimed at it.
- Set limits: steps, tokens, rows returned, and spend.
- Log what was asked, what was called, and what changed.
- Say clearly in the interface what the agent can and cannot answer.
Where to go next
- Part 2: Agents in Microsoft Fabric for notebooks, Data Factory, pipeline operations, repository workflows and data agents.
- Part 3: Agents for Databricks development for Genie Code, Lakeflow, coding agents with skills and MCP, and Genie spaces.
Sources
- Model Context Protocol
- Databricks agent skills, the source for “skills teach the agent how to work; MCP servers let it do the work”
- Databricks Agent Bricks, for supervisor agents
- Fabric data agent concepts, for orchestrators calling data agents
- Operations agent for pipelines
- Claude API documentation, for the SDK calls used in the code above
Code on this page was written for this guide and is illustrative.