Squiggle logoSquiggle
Ecosystem

Basic LLM Prompt

LLM Prompt Example

The following is a prompt that we use to help LLMs, like GPT and Claude, write Squiggle code. This would ideally be provided with the full documentation, for example with this document.

You can read this document in plaintext here.


Write Squiggle code, using the attached documentation for how it works.

Squiggle is a very simple language. Don't try using language primitives/constructs you don't see below, or that aren't in our documentation. They are likely to fail.

When writing Squiggle code, it's important to avoid certain common mistakes.

Syntax and Structure

  1. Variable Expansion: Not supported. Don't use syntax like |v...| or |...v|.
  2. All pipes are "->", not "|>".
  3. Dict keys and variable names must be lowercase.
  4. The last value in a block/function is returned (no "return" keyword).
  5. Variable declaration: Directly assign values to variables without using keywords. For example, use foo = 3 instead of let foo = 3.
  6. All statements in your model, besides the last one must either be comments or variable declarations. You can't do, 4 \n 5 \n 6 Similarly, you can't do, Calculator() ... Table() - instead, you need to set everything but the last item to a variable.
  7. There's no mod operator (%). Use Number.mod() instead.

Function Definitions and Use

  1. Anonymous Functions: Use {|e| e} syntax for anonymous functions.
  2. Function Parameters: When using functions like normal, specify the standard deviation with stdev instead of sd. For example, use normal({mean: 0.3, stdev: 0.1}) instead of normal({mean: 0.3, sd: 0.1}).
  3. There's no recursion.
  4. You can't call functions that accept ranges, with distributions. No, ({|foo: [1,20]| foo}) (4 to 5).

Data Types and Input Handling

  1. Input Types: Use Input.text for numeric inputs instead of Input.number or Input.slider.
  2. The only function param types you can provide are numeric/date ranges, for numbers. f(n:[1,10]). Nothing else is valid. You cannot provide regular input type declarations.
  3. Only use Inputs directly inside calculators. They won't return numbers, just input types.

Looping, Conditionals, and Data Operations

  1. Conditional Statements: There are no case or switch statements. Use if/else for conditional logic.
  2. There aren't for loops or mutation. Use immutable code, and List.map / List.reduce / List.reduceWhile.

List and Dictionary Operations

  1. You can't do "(0..years)". Use List.make or List.upTo.
  2. There's no "List.sort", but there is "List.sortBy", "Number.sort".

Randomness and Distribution Handling

  1. There's no random() function. Use alternatives like sample(uniform(0,1)).
  2. The to syntax only works for >0 values. "4 to 10", not "0 to 10".

Units and Scales

  1. The only "units" are k/m/n/M/t/B, for different orders of magnitude, and "%" for percentage (which is equal to 0.01).

Documentation and Comments

  1. Tags like @name and @doc apply to the following variable, not the full file.
  2. If you use a domain for Years, try to use the Date domain, and pass in Date objects, like Date(2022) instead of 2022.

Dictionaries and Blocks

In Squiggle, you can create dictionaries using two different syntaxes, each with distinct capabilities:

Simple Dictionaries

Use this syntax for basic key-value pairs without internal calculations:

initialCosts = {
  rentDeposit: 5k to 15k,
  equipmentCost: 20k to 40k,
}

For single-value dictionary returns, add a trailing comma. However, this pattern should be done rarely. It's often better to either return all the components inside a components dictionary or similar, or just return the value directly.

Example with trailing comma:

initialCosts = {
  rentDeposit = 5k to 15k
  equipmentCost = 20k to 40k
  total = rentDeposit + equipmentCost
  {total,}
}

Prefer this instead:

initialCostTotal = {
  rentDeposit = 5k to 15k
  equipmentCost = 20k to 40k
  {
    components: {
      rentDeposit,
      equipmentCost
    },
    total: rentDeposit + equipmentCost
  }
}

Or, if you really want to return a single value:

initialCostTotal = {
  rentDeposit = 5k to 15k
  equipmentCost = 20k to 40k
  rentDeposit + equipmentCost
}

Blocks

Use blocks when you need to:

  • Perform calculations with dictionary values
  • Add metadata tags
  • Reference values within the dictionary

Basic block example:

initialCosts = {
  rentDeposit = 5k to 15k
  equipmentCost = 20k to 40k
  total = rentDeposit + equipmentCost
  {components: {rentDeposit, equipmentCost}, total}
}

Adding Tags

The recommended way to add tags is using block syntax:

initialCosts = {
  @name("rent deposit")
  @format("$,.0f")
  rentDeposit = 5k to 15k
 
  @name("equipment cost")
  @format("$,.0f")
  equipmentCost = 20k to 40k
 
  @format("$,.0f")
  total = rentDeposit + equipmentCost
 
  {components: {rentDeposit, equipmentCost}, total}
}

Don't add tags to variables that are only used internally. The tags are only useful when variables are externally accessible.

Common Mistakes to Avoid

  1. Missing return value in blocks:
// Won't work - blocks must return a value
initialCosts = {
  rentDeposit = 5k to 15k
  equipmentCost = 20k to 40k
  total = rentDeposit + equipmentCost
}
  1. Internal references in simple dictionaries:
// Won't work - simple dicts can't reference internal values
initialCosts = {
  rentDeposit: 5k to 15k,
  equipmentCost: 20k to 40k,
  total: rentDeposit + equipmentCost
}

While you can use arrow syntax for tags in dictionary blocks (->Tag.format("$,.0f")), it's generally clearer to use the @tag syntax shown above.

Regular Examples

Here's are some simple example Squiggle programs:

//Model for Piano Tuners in New York Over Time
inputs = {
  @name("🌆 Population of New York in 2022")
  @doc(
    "Population estimate is highly uncertain due to:
- Recent migration patterns post-COVID
- Census undercounting in some neighborhoods
- Illegal immigration estimates
- Different definitions of metro area boundaries
 
Base estimate is 8.1-8.4M with 10% chance of more extreme values 7-9.5M."
  )
  populationOfNewYork2022 = mx([8.1M to 8.4M, 7M to 9.5M], [0.9, 0.1])
 
  @name("🎹 Percentage of Population with Pianos")
  @doc(
    "This estimate is highly uncertain and could vary significantly by neighborhood wealth, cultural factors, and changing music habits over time. The number of digital pianos vs traditional pianos adds additional uncertainty."
  )
  @format(".1%")
  proportionOfPopulationWithPianos = 0.2% to 1%
 
  @name("🔧 Number of Piano Tuners per Piano")
  @doc(
    "This ratio is highly uncertain and could vary based on:
- Piano tuner productivity and work patterns
- Competition and market dynamics
- Geographic density of pianos
- Mix of professional venues vs home users"
  )
  pianoTunersPerPiano = {
    pianosPerPianoTuner = 2k to 50k // (pianos per tuner)
    1 / pianosPerPianoTuner
  }
  {
    populationOfNewYork2022,
    proportionOfPopulationWithPianos,
    pianoTunersPerPiano,
  }
}
//We only mean to make an estimate for the next 10 years.
@hide
domain = [Date(2024), Date(2034)]
 
@name("Population at Time")
populationAtTime(t: domain) = {
  dateDiff = Duration.toYears(t - Date(2024))
  averageYearlyPercentageChange = normal({ p5: -1%, p95: 5% }) // We're expecting NYC to continuously grow with an mean of roughly between -1% and +4% per year
  inputs.populationOfNewYork2022 *
    (averageYearlyPercentageChange + 1) ^ dateDiff
}
 
@name("Total Tuners, at Time")
totalTunersAtTime(t: domain) = populationAtTime(t) *
  inputs.proportionOfPopulationWithPianos *
  inputs.pianoTunersPerPiano
 
meanTunersAtTime(t: domain) = mean(totalTunersAtTime(t))
 
@notebook
@startOpen
summary = [
  "## Summary
  This model estimates the number of piano tuners needed in New York City over time. The estimates are highly uncertain due to complex population dynamics, changing piano ownership patterns, and varying tuning requirements.
 
Key findings:
- Population estimates show significant uncertainty, ranging from **" +
    String(Dist.inv(inputs.populationOfNewYork2022, 0.05), ",.0f") +
    "** to **" +
    String(Dist.inv(inputs.populationOfNewYork2022, 0.95), ",.0f") +
    "** people
- Piano ownership rates vary widely by neighborhood and socioeconomic status
- The ratio of pianos to tuners has high uncertainty due to varying work patterns",
  "## Key Variables",
  "- Population: **" +
    String(Dist.inv(inputs.populationOfNewYork2022, 0.05), ",.0f") +
    " to " +
    String(Dist.inv(inputs.populationOfNewYork2022, 0.95), ",.0f") +
    "** people",
  "- Piano ownership: **" +
    String(mean(inputs.proportionOfPopulationWithPianos) * 100, ".1%") +
    "** of population",
  "## Results for 2024",
  "- Piano ownership: **" +
    String(
      Dist.inv(inputs.proportionOfPopulationWithPianos, 0.05) * 100,
      ".1%"
    ) +
    " to " +
    String(
      Dist.inv(inputs.proportionOfPopulationWithPianos, 0.95) * 100,
      ".1%"
    ) +
    "** of population",
  "- Piano tuners needed: **" +
    String(Dist.inv(totalTunersAtTime(Date(2024)), 0.05), ",.0f") +
    "** to **" +
    String(Dist.inv(totalTunersAtTime(Date(2024)), 0.95), ",.0f") +
    "**",
  "## Major Uncertainties
  - The proportion of households with pianos could change significantly with wealth distribution and cultural shifts
  - Population growth estimates are highly uncertain, especially given recent migration patterns
  - Piano tuning requirements vary widely between professional venues and casual home users
  - Digital piano adoption could significantly impact future demand
 
  ## Results
  The model suggests there will likely be between **" +
    String(Dist.inv(totalTunersAtTime(Date(2024)), 0.05), ",.0f") +
    "** and **" +
    String(Dist.inv(totalTunersAtTime(Date(2024)), 0.95), ",.0f") +
    "** piano tuners needed in 2024.
 
  ## Model Caveats
  - This is a simplified model that assumes piano tuners work full-time
  - Market inefficiencies and geographic distribution aren't considered
  - The model doesn't account for part-time tuners or those with multiple jobs
  - Local economic conditions could significantly impact these estimates",
]
calculator = Calculator(
  {|a, b, c, d| [a, b, c, d]},
  {
    title: "Concat()",
    description: "This function takes in 4 arguments, then displays them",
    sampleCount: 10000,
    inputs: [
      Input.text(
        {
          name: "First Param",
          default: "10 to 13",
          description: "Must be a number or distribution",
        }
      ),
      Input.textArea(
        {
          name: "Second Param",
          default: "[4,5,2,3,4,5,3,3,2,2,2,3,3,4,45,5,5,2,1]",
        }
      ),
      Input.select(
        {
          name: "Third Param",
          default: "Option 1",
          options: ["Option 1", "Option 2", "Option 3"],
        }
      ),
      Input.checkbox({ name: "Fourth Param", default: false }),
    ],
  }
)
x = 10
result = if x == 1 then {
  {y: 2, z: 0}
} else {
  {y: 0, z: 4}
}
y = result.y
z = result.z
@showAs({|f| Plot.numericFn(f, { xScale: Scale.log({ min: 1, max: 100 }) })})
fn(t) = t ^ 2
plot = {|t| normal(t, 2) * normal(5, 3)}
  -> Plot.distFn(
    {
      title: "A Function of Value over Time",
      xScale: Scale.log({ min: 3, max: 100, title: "Time (years)" }),
      yScale: Scale.linear({ title: "Value" }),
      distXScale: Scale.linear({ tickFormat: "#x" }),
    }
  )
f(t: [Date(2020), Date(2040)]) = {
  yearsPassed = toYears(t - Date(2020))
  normal({mean: yearsPassed ^ 2, stdev: yearsPassed^1.3+1})
}
import "hub:ozziegooen/sTest" as sTest
 
@name("Blinds Impact Model")
inputs = {
  @name("Hours of sleep improvement per night (minutes)")
  @doc(
    "How many extra minutes of quality sleep we'd get from better darkness. High uncertainty - could be minimal for people with good existing curtains, or significant for light-sensitive individuals."
  )
  sleepImprovement = mx([5 to 20, normal({ p5: 0, p95: 60 })], [0.8, 0.2])
 
  @name("% of nights where blinds help")
  @doc(
    "We won't get the benefit every night, and sometimes existing curtains would have been fine"
  )
  nightsHelped = 40% to 80%
 
  @name("Productivity multiplier from better sleep")
  @doc(
    "How much more productive we are the next day when we get better sleep. Per hour of extra sleep."
  )
  productivityMultiplier = 1.01 to 1.05
 
  @name("Hours of productive work per day")
  @doc(
    "This varies significantly by profession and work style. Knowledge workers may have 4-6 truly productive hours, while manual laborers may have 6-8."
  )
  productiveHours = 4 to 8
 
  @name("Days worked per year")
  @doc(
    "Accounts for weekends, holidays, vacation, and sick days. May be lower for contractors or higher for workaholics."
  )
  workDays = 200 to 250
 
  {
    sleepImprovement,
    nightsHelped,
    productivityMultiplier,
    productiveHours,
    workDays,
  }
}
 
@name("Annual Impact")
impact = {
  minutesPerNight = inputs.sleepImprovement
  hoursPerNight = minutesPerNight / 60
  nightsPerYear = 365 * inputs.nightsHelped
 
  productivityIncrease = inputs.productivityMultiplier - 1
  dailyHoursImproved = inputs.productiveHours * productivityIncrease *
    hoursPerNight
 
  annualProductiveHoursGained = truncate(
    dailyHoursImproved * inputs.workDays,
    0,
    Number.maxValue
  )
  { annualProductiveHoursGained, }
}
 
blinds_tests = sTest.describe(
  "Blinds Model Tests",
  [
    sTest.test(
      "annual impact is positive",
      {
        ||
        sTest.expect(mean(impact.annualProductiveHoursGained)).toBeGreaterThan(
          0
        )
      }
    ),
    sTest.test(
      "annual impact is reasonable",
      {
        ||
        sTest.expect(mean(impact.annualProductiveHoursGained)).toBeLessThan(50)
      }
    ),
  ]
)
 
@notebook
@startOpen
summary = [
  "## Dark Blinds Impact Analysis",
  "Installing dark blinds could lead to **" +
    String(mean(impact.annualProductiveHoursGained), ",.0f") +
    "** additional productive hours per year (Reference range: " +
    String(quantile(impact.annualProductiveHoursGained, 0.05), ",.0f") +
    " to " +
    String(quantile(impact.annualProductiveHoursGained, 0.95), ",.0f") +
    " hours, with " +
    String(mean(impact.annualProductiveHoursGained), ",.0f") +
    " hours expected).",
  impact.annualProductiveHoursGained,
  "## Key Assumptions & Uncertainties
- Sleep improvement varies significantly by individual circumstances (light sensitivity, current sleep quality)
- The effectiveness depends heavily on existing window coverings
- Productivity gains from better sleep are speculative and highly personal
- Work patterns (productive hours, days worked) vary considerably by profession
 
## Model Components
The model accounts for:
- Daily sleep improvement potential
- Percentage of nights where blinds would help
- Individual productivity response to better sleep
- Available productive hours to be improved
- Working days per year
 
## Notable Considerations
- This model assumes the person has a relatively normal sleep schedule and light exposure
- Results may vary significantly for night shift workers or those in different latitudes
- The small magnitude of improvement might compound significantly over years
- The investment may be particularly valuable for knowledge workers where small cognitive improvements have outsized impacts",
]

On this page