Skip to main content

Fluent LLM

Expressive, opinionated, and intuitive 'fluent interface' Python library for working with LLMs.

Mission statement

Express every LLM interaction in your app prototypes in a single statement, without having to reach for documentation, looking up model capabilities, or writing boilerplate code.

Highlights

  • Expressive: Write natural, readable, and chainable LLM interactions.
  • Opinionated: Focuses on best practices and sensible defaults for LLM workflows.
  • Fluent API: Compose prompts, context, and expectations in a single chain.
  • Supports multimodal (text, image, audio) inputs and outputs: Automatically picks model based on modalities required.
  • Automatic coroutines Can be used both in async and sync contexts.
  • Modern Python: Type hints, async/await, and dataclasses throughout.

Setting API Keys

# On Unix/macOS
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-...

# On Windows (cmd)
set OPENAI_API_KEY=sk-...
set ANTHROPIC_API_KEY=sk-...

# On Windows (PowerShell)
$env:OPENAI_API_KEY="sk-..."
$env:ANTHROPIC_API_KEY="sk-..."

Prompt Builder

The llm global instance is a LLMPromptBuilder instance, which can be used to build prompts.

The following prompt components can be used in an arbitrary order and multiple times:

  • .agent(str): Sets the agent description, defines system behavior.
  • .context(str): Passes textual context to the LLM.
  • .request(str): Passes the main request to the LLM.
  • .image(str): Passes an image to the LLM.
  • .audio(str): Passes an audio file to the LLM.

The prompt chain is terminated by the following methods:

  • .prompt(): str: Sends the prompt to the LLM and expects a text response.
  • .prompt_for_image(): PIL.Image: Sends the prompt to the LLM and expects an image response.
  • .prompt_for_audio(): soundfile.SoundFile: Sends the prompt to the LLM and expects an audio response.
  • .prompt_for_structured_output(pydantic_model): BaseModel: Sends the prompt to the LLM and expects a structured response.

They will either return the desired response if processing was successful, or raise an exception otherwise.

Usage

Callable module

You can use this library as a callable module to experiment with LLMs.

> pip install fluent-llm
> fluent-llm "llm.request('1+2=?').prompt()"
1 + 2 = 3.

Or even easier, without installing, as a tool with uvx:

uvx fluent-llm "llm.request('1+2=?').prompt()"
1 + 2 = 3.

As a library

response = llm \
    .agent("You are an art evaluator.") \
    .context("You received this painting and were tasked to evaluate whether it's museum-worthy.") \
    .image("painting.png") \
    .prompt()
print(response)

Async/await

Just works. See if you can spot the difference to the example above.

response = await llm \
    .agent("You are an art evaluator.") \
    .context("You received this painting and were tasked to evaluate whether it's museum-worthy.") \
    .image("painting.png") \
    .prompt()
print(response)

Multimodality

response = llm
    .agent("You are a 17th century classic painter.")
    .context("You were paid 10 francs for creating a portrait.")
    .request('Create a portrait of Louis XIV.')
    .prompt_for_image()

assert isinstance(response, PIL.Image)
response.show()

Structured output

from pydantic import BaseModel

class PaintingEvaluation(BaseModel):
    museum_worthy: bool
    reason: str

response = llm \
    .agent("You are an art evaluator.") \
    .context("You received this painting and were tasked to evaluate whether it's museum-worthy.") \
    .image("painting.png") \
    .prompt_for_type(PaintingEvaluation)
print(response)

Usage tracking

Usage tracking and price estimations for the last call are built-in.

>>> llm.request('How are you?').prompt()
"I'm doing well, thank you! How about you?"

>>> print(llm.usage)
=== Last API Call Usage ===
Model: gpt-4o-mini-2024-07-18
input_tokens: 11 tokens
output_tokens: 12 tokens

💰 Cost Breakdown:
  input_tokens: 11 tokens → $0.000002
  output_tokens: 12 tokens → $0.000007

💵 Total Call Cost: $0.000009
==============================

>>> llm.usage.cost.total_call_cost_usd
0.000009

>>> llm.usage.cost.breakdown['input_tokens'].count
11

If choosing a provider or model per-invocation is not sufficient, you can define a custom ModelSelectionStrategy and pass it to the LLMPromptBuilder constructor to select provider and model based on your own criteria.

Provider and Model per-prompt override

You can specify preferred providers and models using the fluent chain API:

# Use a specific provider (will select best available model)
response = await llm \
    .provider("anthropic") \
    .request("Hello, how are you?") \
    .prompt()

# Use a specific model
response = await llm \
    .model("claude-sonnet-4-20250514") \
    .request("Write a poem about coding") \
    .prompt()

# Combine provider and model preferences
response = await llm \
    .provider("openai") \
    .model("gpt-4.1-mini") \
    .request("Explain quantum computing") \
    .prompt()

Customization

If the defaults are not sufficient, you can customize the behavior of the builder by creating your own LLMPromptBuilder, instead of using the llm global instance provided for convenience.

However, note that you're probably quickly reaching the point at which you should ask yourself if you're not better off using the official OpenAI Python client library directly. This library is designed to be a simple and opinionated wrapper around the OpenAI API, and it's not intended to be a full-featured LLM client.

Invocation

Instead of using the convenience methods .prompt_*(), you can use the .call() method to execute the prompt and return a response.

Client

Pass in a custom client to the .call() method, to use a custom client for the API call.

Contribution

Setup

uv sync --dev
  • Installs all runtime and development dependencies (including pytest).
  • Requires uv for fast, modern Python dependency management.

Running Tests

All tests are run with uv:

uv run pytest

Mocked Tests

  • Located in tests/test_mocked.py.
  • Do not require a real OpenAI API key or network access.
  • Fast and safe for CI or local development.

Live API Tests

  • Located in tests/test_live_api_*.py.
  • Require a valid API KEY and internet access.
  • Will consume credits!
  • Run only when you want to test real OpenAI integration.

License

Licensed under the MIT License.

Disclaimer

Almost all code written by Claude, o3 and SWE-1, concept and design by @hheimbuerger.

Metadata

Release files for fluent-llm 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fluent-llm 1.1.0
File Size Uploaded Provenance Actions
fluent_llm-1.1.0.tar.gz 28.8 kB Without provenance Details for fluent_llm-1.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for fluent-llm 1.1.0
File Interpreter ABI Platform Actions
fluent_llm-1.1.0-py3-none-any.whl Python 3 none any Details for fluent_llm-1.1.0-py3-none-any.whl

Total release size: 55.3 kB

Release files / fluent_llm-1.1.0.tar.gz

Download URL fluent_llm-1.1.0.tar.gz
Size 28.8 kB
Tags Source
SHA-256 checksum
How to use checksums
17d613db43b7239d9a1ad9c738f57284bd9824021ce9adc8dd0c8f5ed027ca5c
BLAKE2b-256 checksum
How to use checksums
4b6dcaf0253bac780def3a9e073878b6aa3747203bf7a9e965f1bd00317064fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.0

Release files / fluent_llm-1.1.0-py3-none-any.whl

Download URL fluent_llm-1.1.0-py3-none-any.whl
Size 26.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8b28251bc677d998fc55ade600c81af2d3cff8254d2148598479a6ba9199084d
BLAKE2b-256 checksum
How to use checksums
8977e08f65736a6df15000fa5cc8dfa7dee3b21565d662be79da98b4cafbcb67
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.7.0

Security for fluent-llm 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page