Skip to main content
The LLM provider integrates language models into Colin’s template system. Through extract(), classify(), and complete() functions, you can transform, analyze, and synthesize content during compilation. Results are cached by input hash, making subsequent compilations fast and predictable. The LLM provider is registered automatically—no configuration required. It uses pydantic-ai under the hood and supports any model that pydantic-ai can connect to.

Configuration

Configure a default model in colin.toml:
colin.toml
[[providers.llm]]
model = "anthropic:claude-sonnet-4-5"
Model identifiers follow the provider:model-name format. Supported providers include anthropic and openai.
FieldDescriptionDefault
modelModel identifier (e.g., anthropic:claude-sonnet-4-5)From settings
instructionsSystem prompt for all LLM calls from this providerNone
instructions_refPath to a document whose compiled content becomes the system promptNone

Named Providers

Beyond the default provider, you can configure named LLM instances for different use cases. A provider entry without a name becomes the default; entries with a name become accessible under that namespace.
colin.toml
# Default LLM - used by llm.extract(), llm.classify(), {% llm %}, etc.
[[providers.llm]]
model = "anthropic:claude-sonnet-4-5"

# Fast model for simple extractions
[[providers.llm]]
name = "fast"
model = "anthropic:claude-haiku-4-5"

# Capable model for complex reasoning
[[providers.llm]]
name = "capable"
model = "anthropic:claude-opus-4-5"
The default provider’s functions are available on colin.llm:
{{ colin.llm.extract(content, 'summary') }}
{{ colin.llm.classify(content, ['positive', 'negative']) }}

{% llm %}
Analyze this content...
{% endllm %}
Named providers are accessed through their name:
{{ colin.llm.fast.extract(content, 'word count') }}
{{ colin.llm.capable.extract(content, 'detailed legal analysis') }}
This lets you optimize cost and latency by routing simple tasks to faster, cheaper models while reserving capable models for complex work.

Instructions

Instructions act as system prompts, shaping how the LLM responds across all calls. Set them at the provider level:
colin.toml
[[providers.llm]]
model = "anthropic:claude-sonnet-4-5"
instructions = "You are a technical writer. Be concise and precise."
For longer instructions, reference a compiled document:
colin.toml
[[providers.llm]]
model = "anthropic:claude-sonnet-4-5"
instructions_ref = "prompts/analyst.md"
The referenced document is compiled first, and its output becomes the system prompt. This lets you maintain complex instructions as standalone files. Override instructions on individual calls when needed:
{{ ref('data') | llm_extract('summary', instructions='Respond in Spanish.') }}

{% llm instructions='Write in a casual, friendly tone.' %}
Explain this concept: {{ ref('topic').content }}
{% endllm %}
Call-level instructions take precedence over provider-level settings.

Template Functions

The LLM provider exposes three functions accessible through the colin.llm namespace (colin.llm.extract(), colin.llm.classify(), colin.llm.complete()).
FunctionPurpose
extract()Pull specific information from content
classify()Categorize content into predefined labels
complete()Generate text from a prompt
Individual function calls can override the model:
{{ colin.llm.extract(content, 'summary', model='anthropic:claude-haiku-4-5') }}

Extract

The extract() function pulls specific information from content using an LLM. Pass the content and a prompt describing what to extract:
models/summary.md
---
name: Key Points
---

{{ extract(ref('sources/report').content, 'the top 5 key findings') }}
The function works as a filter for more readable chaining:
{{ ref('sources/report') | llm_extract('the top 5 key findings') }}
Both syntaxes are equivalent. The filter syntax reads more naturally when working with refs. Apply multiple extractions to distill different aspects of a document:
models/brief.md
---
name: Executive Brief
---

## Summary
{{ ref('sources/report') | llm_extract('a one-paragraph executive summary') }}

## Risks
{{ ref('sources/report') | llm_extract('identified risks and mitigation strategies') }}

## Action Items
{{ ref('sources/report') | llm_extract('immediate action items with owners') }}
When you pass a RefResult to extract(), Colin includes document metadata in the prompt context. The LLM sees the document name, description, URI, and content—helping it understand the source material.

Classify

The classify() function categorizes content into predefined labels:
{% set sentiment = colin.llm.classify(ref('feedback').content, ['positive', 'negative', 'neutral']) %}

Customer sentiment: {{ sentiment }}
The function returns a single label from the provided list. For yes/no decisions, use boolean labels:
{% set urgent = colin.llm.classify(ref('ticket').content, [true, false]) %}

{% if urgent %}
**URGENT**: This ticket requires immediate attention.
{% endif %}
Enable multiple labels with multi=True:
{% set tags = colin.llm.classify(ref('article').content, ['technical', 'business', 'tutorial', 'news'], multi=true) %}

Tags: {{ tags | join(', ') }}
This returns a list of applicable labels rather than a single choice.

Complete

The complete() function generates text from a prompt:
{{ colin.llm.complete('Write a haiku about documentation') }}
For multi-line prompts and complex generation, use {% llm %} blocks instead:
{% llm %}
Write a professional summary of this technical document:

{{ ref('sources/spec').content }}

Focus on practical implications for developers.
{% endllm %}
Override the model for specific completions:
{% llm model="anthropic:claude-haiku-4-5" %}
Quick summary: {{ ref('data').content }}
{% endllm %}

Caching

Colin caches LLM results by input hash. For extract() and classify(), the hash includes content and prompt. For complete() and {% llm %} blocks, it’s the fully rendered prompt. If content or prompt changes, the cache misses and Colin makes a fresh call. During compilation, Colin shows cache status:
✓ summary.md
  → llm:a1b2c3    # Fresh LLM call
  ⚡ llm:d4e5f6   # Cache hit
Force fresh LLM calls by cleaning the manifest:
colin clean    # Removes manifest and cache
colin run      # Full recompilation
Or bypass cache for specific documents:
colin run context/analysis.md --no-cache

Cost Management

LLM calls have associated costs. Colin’s caching minimizes redundant calls, but you can further optimize by using cheaper models for simple tasks:
{{ ref('data') | llm_extract('word count', model='anthropic:claude-haiku-4-5') }}
Structure documents to avoid redundant extraction. Extract once into an intermediate document:
models/context/extracted.md
---
name: Extracted Data
---

{{ ref('sources/raw') | llm_extract('structured summary') }}
Other documents can then reference the extracted content without re-running the LLM:
{{ ref('context/extracted').content }}