Skip to content

Tutorial (10 Minutes)

Audience: developers new to Alloy who want a fast first run. Outcome: run ask(), write a command, add a type, call a tool, use a contract, stream text, and switch providers. Time: ~10 minutes Prereqs: Python 3.10+. pip install alloy-ai (or pip install 'alloy-ai[providers]'). Set a provider key, or use ALLOY_BACKEND=fake for offline.

Outcomes first

By the end you’ll have:

  • A working ask()
  • Your first @command
  • One typed output
  • One safe tool
  • A small workflow guarded by contracts

1) Explore with ask()

What you’ll learn: freeform Q&A for quick exploration.

Code (tutorial_step1.py)

from alloy import ask

print(ask("Say hello!"))

Run

python tutorial_step1.py

You'll see: a short greeting.


2) First @command (text)

What you’ll learn: the function returns a prompt string; the decorator runs the model.

Code (tutorial_step2.py)

from alloy import command

@command  # returns str by default
def summarize(text: str) -> str:
    return f"Summarize in one sentence: {text}"

print(summarize("Alloy lets you write typed AI functions in Python."))

Run: python tutorial_step2.py

You'll see: a one‑sentence summary.


3) Enforce a type (dataclass)

What you’ll learn: provider‑enforced typed output; clear errors if shape is wrong.

Code (tutorial_step3.py)

from dataclasses import dataclass
from alloy import command

@dataclass
class ArticleSummary:
    title: str
    key_points: list[str]
    reading_time_minutes: int

@command(output=ArticleSummary)
def summarize_article(text: str) -> str:
    return f"""
    Summarize with: title, 3–5 key_points, reading_time_minutes.
    Article: {text}
    """

res = summarize_article("Python emphasizes readability and has a vast ecosystem.")
print(res.title)

Run: python tutorial_step3.py

You'll see: a structured object; IDE autocompletes fields.


4) Add a tool (plain Python)

What you’ll learn: give the model a small local capability.

Code (tutorial_step4.py)

from alloy import command, tool

@tool
def word_count(text: str) -> int:
    return len(text.split())

@command(tools=[word_count])
def analyze(text: str) -> str:
    return f"Use word_count(text), then suggest one clarity improvement.\nText: {text}"

print(analyze("Alloy makes typed AI functions feel like normal Python."))

Run: python tutorial_step4.py

You'll see: a short analysis referencing the word count.


5) Add a contract (DBC: require/ensure)

What you’ll learn: enforce order/invariants with fast, actionable feedback.

Code (tutorial_step5.py)

import datetime
from alloy import command, tool, require, ensure

@tool
@ensure(lambda d: isinstance(d, dict) and "validated_at" in d, "Must add validated_at")
def validate_data(data: dict) -> dict:
    d = dict(data)
    d["validated_at"] = datetime.datetime.now(datetime.timezone.utc).isoformat()
    return d

@tool
@require(lambda ba: "validated_at" in ba.arguments.get("data", {}), "Run validate_data first")
@ensure(lambda ok: ok is True, "Save must succeed")
def save_to_production(data: dict) -> bool:
    return True

@command(output=str, tools=[validate_data, save_to_production])
def process_order(order: dict) -> str:
    return f"Validate then save this order: {order}"

print(process_order({"id": 123, "amount": 99.99}))

Run: python tutorial_step5.py

You'll see: a short message indicating the steps; contracts guide the model.


6) Streaming (text‑only today)

What you’ll learn: stream text chunks; typed outputs/tools don’t stream.

Code (tutorial_step6.py)

from alloy import ask, command

@command
def brainstorm(topic: str) -> str:
    return f"Write a short riff about: {topic}"

for chunk in ask.stream("Write a one-liner about cats"):
    print(chunk, end="")
print()

for chunk in brainstorm.stream("Alloy examples"):
    print(chunk, end="")
print()

Run: python tutorial_step6.py

You'll see: streamed text printed inline for both loops (two short lines).

Note: Commands with tools or typed outputs don’t stream. Details: Guide → Streaming.


7) Switch providers (no code changes)

What you’ll learn: portability — same code, different models.

Run one of the following (pick a provider you’ve configured):

export OPENAI_API_KEY=...;   ALLOY_MODEL=gpt-5-mini            python tutorial_step3.py
export ANTHROPIC_API_KEY=...; ALLOY_MODEL=claude-sonnet-4-20250514 python tutorial_step3.py
export GOOGLE_API_KEY=...;    ALLOY_MODEL=gemini-2.5-flash      python tutorial_step3.py
ALLOY_MODEL=ollama:<model>    python tutorial_step3.py

Expected: the same structured result across providers (style may vary).


Next steps - Examples (curated) - Providers (capability matrix + setup) - Configuration (precedence and overrides) - Common Pitfalls