promptfn 0.1.0
Turn Python functions into LLM prompts
Requires Python: >=3.10

# promptfn

Turn Python functions into LLM prompts with zero config. Use Jinja2 templates, get type-safe Pydantic responses, and work with 100+ LLM providers.

---

**Quick Links:**
- [Quick Start](#quick-start) - Get started in 30 seconds
- [Configuration](#configuration) - Custom parameters, presets, and advanced options
- [Callbacks](#callbacks) - Hook into completion events
- [Features](#features) - See what's possible

---

## Quick Start

```python
from promptfn import prompt
from pydantic import BaseModel


class ResearchResult(BaseModel):
    text: str


@prompt(model="openai/gpt-4o")
def research_topic(
    topic: str,
    level: str = "basic"
) -> ResearchResult:
    """
    - role: system
      content: You are a helpful research assistant.

    - role: user
      content: >
        Analyze this topic: {{ topic }}, Knowledge level: {{ level | capitalize }}.
        Provide a clear and informative overview.
    """

result = research_topic(topic="AI")
print(result.text)
```

**Output:**
```
Artificial Intelligence (AI) refers to the simulation of human intelligence in machines
that are programmed to think and learn. At a basic level, AI encompasses machine learning,
natural language processing, and computer vision. Modern AI systems can perform tasks such
as recognizing speech, making decisions, and identifying patterns in data.
```

## Configuration

### Custom Parameters

Use `LLMConfig` to set universal parameters (work with all providers):

```python
from promptfn import prompt
from promptfn import LLMConfig
from pydantic import BaseModel


class Summary(BaseModel):
    text: str


@prompt(
    model="anthropic/claude-3-5-sonnet-20241022",
    config=LLMConfig(
        temperature=0.3,  # Randomness (0-2)
        max_tokens=1000   # Max output length
    )
)
def summarize(
    text: str,
    style: str = "professional",
    words: int = 50
) -> Summary:
    """
    - role: system
      content: You are an expert text summarizer.

    - role: user
      content: >
        Summarize the following text in {{ style | capitalize }} style.
        Keep it under {{ words }} words.
        {% if style | lower == "casual" %}
        Use simple language and be conversational.
        {% else %}
        Be formal and precise.
        {% endif %}

        Text:
        {{ text }}
    """

# read text from file
with open("article.txt") as f:
    text = f.read()

result = summarize(
    text=text,
    style="casual",
    words=30
)

print(result.text)
```

### Provider-Specific (Advanced)

For expert use cases, use `extra` to pass provider-specific parameters:

```python
from promptfn import prompt
from promptfn import LLMConfig
from pydantic import BaseModel


class Analysis(BaseModel):
    summary: str
    key_points: list[str]


@prompt(
    model="anthropic/claude-3-opus",
    config=LLMConfig(
        temperature=0.7,
        extra={
            "top_k": 40  # Anthropic sampling parameter
        }
    )
)
def analyze(text: str) -> Analysis:
    """
    - role: system
      content: You are an analytical assistant.

    - role: user
      content: >
        Analyze this text: {{ text }}
    """
```

### Presets

For common use cases, use pre-configured settings:

```python
from promptfn import prompt
from promptfn import LLMConfigPreset
from pydantic import BaseModel


class BlogPost(BaseModel):
    title: str
    content: str


@prompt(
    model="openai/gpt-4o-mini",
    config=LLMConfigPreset.CREATIVE
)
def write_blog_post(topic: str) -> BlogPost:
    """
    - role: system
      content: You are a creative blog writer.

    - role: user
      content: >
        Write an engaging blog post about {{ topic }}.
    """

result = write_blog_post("Generative AI")
print(result.content)
```

**Available Presets:**
- `LLMConfigPreset.DEFAULT` - Balanced output (temperature=0.7)
- `LLMConfigPreset.CREATIVE` - Imaginative and diverse (temperature=0.9, top_p=0.95)
- `LLMConfigPreset.PRECISE` - Focused and factual (temperature=0.3, top_p=0.9)
- `LLMConfigPreset.DETERMINISTIC` - Reproducible results (temperature=0.0, top_p=1.0)

## Callbacks

```python
from promptfn import prompt
from pydantic import BaseModel


class Analysis(BaseModel):
    sentiment: str
    score: float


def log_result_callback(result: Analysis) -> Analysis:
    print(f"✓ Analysis complete: {result.sentiment} ({result.score})")
    return result


@prompt(
    model="openai/gpt-4o-mini",
    on_complete=log_result_callback
)
def analyze_sentiment(text: str) -> Analysis:
    """
    - role: system
      content: You are a sentiment analysis expert.

    - role: user
      content: >
        Analyze the sentiment of: {{ text }}
        Return sentiment (positive/negative/neutral) and confidence score (0-1).
    """

analyze_sentiment("I love this product!")  # callback handles the result
```

## Debug Logging

Enable debug mode to see detailed LLM call logs:

```python
import promptfn

# Enable debug logging
promptfn.enable_debug_mode()

# Disable debug logging
promptfn.disable_debug_mode()
```

## Features

- **Decorator API** - Turn functions into LLM calls with `@prompt`
- **Async Support** - Automatic detection and handling of sync/async functions
- **Jinja2 Templates** - Use docstrings as Jinja2 templates with `{{ variable }}` syntax, conditionals, loops and more
- **Structured Outputs** - Automatic validation using Pydantic models
- **Provider-Specific Parameters** - Use `extra` for advanced features (frequency_penalty, top_k, etc.)
- **Config Presets** - Pre-configured settings (DEFAULT, CREATIVE, PRECISE, DETERMINISTIC)
- **Debug Logging** - Built-in logging for development and debugging
- **Callbacks** - Hook into completion events with `on_complete`
- **Multi-Provider** - Supports 100+ LLMs via LiteLLM