falaw.operations.llm#

LLM-driven tools: text completion, screenplay → Scene IR, IR editing.

These wrap fal-ai/any-llm so the agent (or the user) can invoke an LLM inline as part of a falaw workflow — e.g. parsing a treatment into a structured Scene, or rewriting a Beat from a directorial note.

We intentionally keep these thin. The contract is: any-llm is the universal LLM endpoint, you pick the model via model, and we return plain Python (str or parsed dict).

Functions

apply_note_to_beat(beat, note, *[, model])

Use the LLM to apply a directorial note to a Beat.

apply_note_to_scene(scene, note, *[, model])

Apply a cross-cutting note: LLM proposes per-beat edits, we apply them.

llm_complete(prompt, *[, system, model, ...])

Single-shot LLM completion.

llm_complete_with_receipt(prompt, *[, ...])

Single-shot LLM completion that also reports what it cost.

parse_screenplay(text, *[, title, style, model])

Convert prose screenplay text into a Scene IR via an LLM call.

Classes

LlmReceipt(application, model, cache_hit, ...)

What one eager LLM call cost — the record the eager path never kept.

class falaw.operations.llm.LlmReceipt(application, model, cache_hit, chars_in, chars_out, tokens_in=None, tokens_out=None, estimated_cost_usd=None, cost_source='unknown')[source]#

Bases: object

What one eager LLM call cost — the record the eager path never kept.

The falaw half of falaw#50’s option B: the eager llm_complete() returned a bare string, so LLM spend was invisible to every accounting surface — not because estimates were None, but because *nothing was recorded at all* (the field signature: real dollars spent beside plans: 0). A caller that books receipts uses llm_complete_with_receipt() and gets this alongside the text.

Field semantics, honest by construction:

  • chars_in / chars_out are measured facts (prompt+system in, response text out).

  • tokens_in / tokens_out are best-effort from the raw response’s usage block (OpenAI prompt_tokens/completion_tokens and Anthropic input_tokens/output_tokens shapes). None means unrecorded by the provider response, never zero.

  • estimated_cost_usd is 0.0 on a cache hit (nothing was billed), else fal’s published per-request price for the routed model, read off the rate table (falaw.llm_ceiling_usd()) — an estimate, not an observed bill. It is the routed model and not the fal-ai/any-llm record because the record carries fal’s standard tier, and a premium routed model bills ten times that: pricing every turn from the record under-reported premium spend tenfold in the ledger a consumer sums (falaw#55). None only when nothing can price it at all.

  • cost_source says which of those cases produced the number, so a receipt consumer never has to guess: "cache_hit", "rate_table:per_call", "registry:<kind>" (the fallback for a model the table does not price), or "unknown".

falaw.operations.llm.apply_note_to_beat(beat, note, *, model='anthropic/claude-sonnet-4.5')[source]#

Use the LLM to apply a directorial note to a Beat.

Return type:

Beat

falaw.operations.llm.apply_note_to_scene(scene, note, *, model='anthropic/claude-sonnet-4.5')[source]#

Apply a cross-cutting note: LLM proposes per-beat edits, we apply them.

Return type:

Scene

falaw.operations.llm.llm_complete(prompt, *, system='', model='anthropic/claude-sonnet-4.5', temperature=0.7, extra=None)[source]#

Single-shot LLM completion. Returns the assistant text.

Delegates to llm_complete_with_receipt() (one argument-construction site, so the two spellings share one cache identity) and discards the receipt. A caller that accounts for spend wants the receipt variant.

Return type:

str

falaw.operations.llm.llm_complete_with_receipt(prompt, *, system='', model='anthropic/claude-sonnet-4.5', temperature=0.7, extra=None)[source]#

Single-shot LLM completion that also reports what it cost.

Same arguments, same wire call, same cache identity as llm_complete() — the receipt is a side channel, observed from the call this function was making anyway (the cache layer’s cache_hit event, the response’s usage block, the registry’s estimate). Library surface only — deliberately NOT a registered tool: the tool surface keeps the one string-returning llm_complete.

Return type:

tuple[str, LlmReceipt]

falaw.operations.llm.parse_screenplay(text, *, title='', style='', model='anthropic/claude-sonnet-4.5')[source]#

Convert prose screenplay text into a Scene IR via an LLM call.

Return type:

Scene