Metadata-Version: 2.4
Name: autoform
Version: 0.0.7
Summary: Composable function transformations for LLM programs
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: litellm>=1.80.9
Requires-Dist: optree>=0.18.0
Dynamic: license-file


<div align="center">

# `autoform`

**Composable function transformations for LLM programs**

[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![CI](https://github.com/ASEM000/autoform/actions/workflows/ci.yml/badge.svg)](https://github.com/ASEM000/autoform/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/ASEM000/autoform/graph/badge.svg?token=Z0JBHSC3ZK)](https://codecov.io/gh/ASEM000/autoform)
[![Documentation](https://readthedocs.org/projects/autoform/badge/?version=latest)](https://autoform.readthedocs.io)
[![License](https://img.shields.io/badge/License-Apache_2.0-green.svg)](LICENSE)
[![Downloads](https://pepy.tech/badge/autoform)](https://pepy.tech/project/autoform)
[![DOI](https://zenodo.org/badge/1115015093.svg)](https://doi.org/10.5281/zenodo.18071950)

</div>


```bash
pip install autoform
```

## Example

Batched semantic gradients: feedback on N outputs → feedback on N inputs.

```python
import autoform as af

def answer(query):
    return af.lm_call(f"Answer: {query}", model="gpt-4o")

ir = af.build_ir(answer)("...") # trace

# 3 queries + 3 critiques -> 3 answers + 3 improvement hints
queries = ["What is AI?", "Explain DNS", "Define recursion"]
critiques = ["too technical", "too long", "perfect"]

batched_pb = af.batch(af.pullback(ir), in_axes=(True, True)) # compose

answers, hints = af.call(batched_pb)((queries, critiques))
```

Trace once. Batch it. Differentiate it. **Compose them.**

## Why

LLM programs are hard to optimize:
- **debugging**: which agent caused the bad output?
- **optimization**: how do you improve prompts systematically?
- **batching**: how do you run N inputs without rewriting code?

autoform solves this with **function transformations**. Trace once, transform freely.


## Full Example

Multi-agent pipeline with checkpoints, batching, gradients, and debugging:

```python
import autoform as af

class Verdict(af.Struct):
    decision: str
    reasoning: str

def judge_debate(topic: str) -> Verdict:
    """Three agents debate, one judges."""

    # agent 1: argue for
    pro = af.format("Argue FOR: {}", topic)
    pro = af.checkpoint(pro, key="pro", collection="debug")
    msgs = [dict(role="user", content=pro)]
    pro = af.lm_call(msgs, model="gpt-4o")

    # agent 2: argue against  
    con = af.format("Argue AGAINST: {}", topic)
    con = af.checkpoint(con, key="con", collection="debug")
    msgs = [dict(role="user", content=con)]
    con = af.lm_call(msgs, model="gpt-4o")

    # agent 3: judge
    prompt = af.format("PRO: {}\nCON: {}\nWho wins?", pro, con)
    prompt = af.checkpoint(prompt, key="judge", collection="debug")
    msgs = [dict(role="user", content=prompt)]
    return af.struct_lm_call(msgs, model="gpt-4o", struct=Verdict)

ir = af.build_ir(judge_debate)("...")  # trace (no execution)

# run once
verdict = af.call(ir)("pineapple on pizza")

# batch: parallel topics
batched = af.batch(ir, in_axes=True)
verdicts = af.call(batched)(["pineapple on pizza", "cats vs dogs", "tabs vs spaces"])

# gradients: feedback -> input improvement
pb_ir = af.pullback(ir)
verdict, grad = af.call(pb_ir)(("pineapple on pizza", Verdict(decision="biased", reasoning="pro was weak")))

# collect: capture checkpoint values
verdict, captured = af.collect(ir, collection="debug")("pineapple on pizza")

# inject: override checkpoint values
verdict = af.inject(ir, collection="debug", values=captured)("pineapple on pizza")

# explain how batches are placed
in_axes = (True, Verdict.model_construct(decision=True, reasoning=True))

# compose freely
batched_grads = af.batch(af.pullback(ir), in_axes=in_axes)
```


> ⚠️ **early development**: API may change.
