Skip to main content

PrivacyForms AI

Tests Lint Build

A Python CLI tool for interacting with Large Language Models (LLMs) via Simon Willison's llm library. Supports multiple providers including OpenAI, Anthropic, Moonshot, and Ollama.

Features

  • 🔧 Simple CLI - Easy-to-use command-line interface with colored output
  • 💬 Interactive Chat - Multi-turn conversations with context/memory
  • 🚀 Multiple Providers - Works with OpenAI, Anthropic, Moonshot, Ollama, and more
  • 🧪 Well Tested - 100 % test coverage enforced via make test-cov
  • Fast - Built with modern Python tooling
  • 🔍 Observable - Optional verbose logging (-v for metadata, -vv for full prompt payloads) to inspect prompt payloads

Installation

# Clone the repository
git clone https://github.com/zopyx/privacyforms.ai.git
cd privacyforms.ai

# Install with uv
uv sync

# Or install in development mode
uv sync --all-extras --dev

Using pip

pip install privacyforms-ai

Configuration

Set your API keys as environment variables:

# OpenAI
export OPENAI_API_KEY="your-key"

# Anthropic
export ANTHROPIC_API_KEY="your-key"

# Moonshot
export MOONSHOT_API_KEY="your-key"

For Ollama, make sure the Ollama server is running locally.

Usage

Global Options

# Show version
privacyforms-ai --version

# Show help
privacyforms-ai --help

# Enable verbose output (shows prompt logs on stderr)
privacyforms-ai -v models

# Enable debug output
privacyforms-ai -vv prompt gpt-4o-mini "Hello!"

List Available Models

privacyforms-ai models

# JSON output
privacyforms-ai models --json-output

Send a Single Prompt

# Basic prompt
privacyforms-ai prompt gpt-4o-mini "What is the capital of France?"

# With system prompt
privacyforms-ai prompt gpt-4o-mini "Explain recursion" --system "You are a computer science tutor"

# With file attachment
privacyforms-ai prompt gpt-4o-mini "Summarize this" -a document.pdf

# Start interactive chat with file attachment
privacyforms-ai chat gpt-4o-mini -a document.pdf

# Use python -m
python -m privacyforms_ai --help

Interactive Chat

Start an interactive chat session with conversation history:

# Basic chat
privacyforms-ai chat moonshot/kimi-k2.5

# With system prompt
privacyforms-ai chat gpt-4o-mini -s "You are a helpful coding assistant"

Chat Commands:

  • /quit, /exit, /q - End the chat session
  • /clear - Clear conversation history
  • /model - Show current model

Example session:

Starting chat with model: moonshot/kimi-k2.5
Type /quit, /exit, or /q to end the session. Type /clear to reset history.
--------------------------------------------------

You: Hello!

AI: Hello! How can I help you today?

You: What can you do?

AI: I can help with a variety of tasks including...

You: /quit

Goodbye!

Custom OpenAI-compatible Endpoints

Besides the providers registered through llm, the Python API can talk to any OpenAI-compatible endpoint by passing an (api_url, api_key, model_name) triple — for example DeepSeek, Groq, Together, or a local vLLM/LiteLLM proxy:

from pathlib import Path

from privacyforms_ai import AI

model = AI.get_custom_model(
    model_name="deepseek-v4-pro",
    api_url="https://api.deepseek.com",
    api_key=Path("deepseekv4.token").read_text().strip(),
)
response = AI.send_prompt(model, "Hello!")
print(AI.extract_response_text(response))

For multi-turn conversations use AI.get_custom_conversation():

conversation = AI.get_custom_conversation(
    model_name="deepseek-v4-pro",
    api_url="https://api.deepseek.com",
    api_key=Path("deepseekv4.token").read_text().strip(),
    system="You are a helpful assistant.",
)
response = AI.send_conversation_prompt(conversation, "Hello!")
print(AI.extract_response_text(response))

Pass vision=True to get_custom_model() for endpoints whose models accept image attachments. Keep token files like deepseekv4.token out of version control — the repo's .gitignore already covers this one.

Authentication note: the api_key passed to get_custom_model() is sent to the endpoint as-is. Internally the model keeps llm's needs_key flag truthy so that the explicitly passed key is used. Do not set model.needs_key = None on the returned model: llm then treats the model as key-less and substitutes the literal placeholder DUMMY_KEY as the Bearer token, which the endpoint rejects with HTTP 401 (DeepSeek reports this as Your api key: ****_KEY is invalid — the _KEY suffix is the placeholder, not your key).

A ready-made smoke test for the DeepSeek endpoint lives at scripts/deepseek_smoke.py (run with uv run python scripts/deepseek_smoke.py; requires a valid key in deepseekv4.token and network access).

Python API

All functionality is exposed through the AI class:

Method Description
AI.get_models() List all registered models as {key, name, provider} dicts
AI.get_model(key) Fetch a registered llm model by its key
AI.get_conversation(model_key, system=None) Start a multi-turn conversation with a registered model
AI.get_custom_model(model_name, api_url, api_key, *, vision=False, can_stream=True) Create a model for an arbitrary OpenAI-compatible endpoint
AI.get_custom_conversation(model_name, api_url, api_key, system=None, *, vision=False) Start a conversation with a custom endpoint
AI.send_prompt(model, prompt, system=None, attachments=None) Send a single prompt; returns the llm response object
AI.send_conversation_prompt(conversation, prompt, attachments=None) Continue a conversation
AI.extract_response_text(response) Extract plain text from an llm response
AI.create_attachment(file_path, mime_type=None) Build an attachment from a local file (MIME type auto-detected)
AI.prompt_with_attachment(model, prompt, file_path, mime_type=None) Send a prompt with a file attachment, returns the response text

Example covering the full lifecycle:

from privacyforms_ai import AI

# 1. List registered models
for m in AI.get_models():
    print(m["key"], "-", m["name"], f"({m['provider']})")

# 2. Registered model, single prompt
model = AI.get_model("gpt-4o-mini")
response = AI.send_prompt(model, "Hello!", system="Be concise.")
print(AI.extract_response_text(response))

# 3. Registered model, multi-turn conversation
conversation = AI.get_conversation("gpt-4o-mini", system="You are a helpful assistant.")
print(AI.extract_response_text(AI.send_conversation_prompt(conversation, "What is 2+2?")))

# 4. File attachments
print(AI.prompt_with_attachment(model, "Summarize this", "document.pdf"))

llm response objects are lazy: the network call only happens when the response is consumed (e.g. via AI.extract_response_text(response)).

Development

Setup

# Clone and setup
git clone https://github.com/zopyx/privacyforms.ai.git
cd privacyforms.ai
uv sync --all-extras --dev
source .venv/bin/activate

Running Tests

# Run all tests
make test

# With coverage
make test-cov

# Verbose output
uv run pytest -v

Code Quality

# Format code
make format

# Check formatting
make format-check

# Lint
make lint

# Auto-fix linting issues
make fix

# Type check
make type-check

# Run the full local gate
make check

Build Package

# Build release artifacts into dist/
make dist

Upload Package

# Upload to PyPI using twine and your ~/.pypirc or TWINE_* credentials
make upload

# Upload to another configured repository, e.g. TestPyPI
make upload TWINE_REPOSITORY=testpypi

Create a Release

# 1. Update the version in pyproject.toml, src/privacyforms_ai/_version.py, README, and tests

# 2. Refresh the lockfile if needed
uv sync --all-extras --dev

# 3. Verify and build
make check
make dist

# 4. Upload
make upload

# 5. Commit and tag
git add pyproject.toml src/privacyforms_ai/_version.py src/privacyforms_ai/__init__.py tests/ uv.lock CHANGELOG.md .gitattributes LICENSE
git commit -m "Release X.Y.Z"
git tag vX.Y.Z
git push origin HEAD
git push origin vX.Y.Z

Project Structure

privacyforms.ai/
├── src/privacyforms_ai/
│   ├── __init__.py
│   ├── _version.py        # Package version
│   ├── ai.py              # AI class for LLM interactions
│   └── cli.py             # Click CLI commands
├── tests/
│   ├── conftest.py        # Pytest fixtures
│   ├── test_ai.py         # AI class tests
│   └── test_cli.py        # CLI tests
├── pyproject.toml         # Project configuration
├── uv.lock               # Locked dependencies
├── CHANGELOG.md          # Release notes
├── LICENSE               # MIT license
├── .gitattributes        # Line-ending configuration
└── README.md

CI/CD

GitHub Actions workflow runs on:

  • Python 3.12, 3.13, 3.14, 3.14t (free-threaded)
  • Ubuntu Linux

Jobs:

  • test - Run pytest with coverage
  • lint - ruff (formatting, linting) and ty (type checking)
  • build - Build package artifacts and validate with twine

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Ensure all checks pass (make check)
  5. Submit a pull request

Acknowledgments

Release files for privacyforms.ai 0.1.8

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

Source distribution (sdist)

Source distribution for privacyforms.ai 0.1.8
File Size Uploaded
privacyforms_ai-0.1.8.tar.gz 20.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for privacyforms.ai 0.1.8
File Interpreter ABI Platform
privacyforms_ai-0.1.8-py3-none-any.whl Python 3 none any Details

Total release size: 33.4 kB

Release files / privacyforms_ai-0.1.8.tar.gz

Download URL privacyforms_ai-0.1.8.tar.gz
Size 20.2 kB
Tags Source
SHA-256 checksum
How to use checksums
fdaf7948af053ac1ec5905d2291e7efc1a9b8101a9b8ae2297dcda56e0baeb52
BLAKE2b-256 checksum
How to use checksums
b12a020c4f7ba1128b6fb1e9908578d7e9d0ba11cffbf872c4c02b1dfd2180ae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.

Transparency log

Release files / privacyforms_ai-0.1.8-py3-none-any.whl

Download URL privacyforms_ai-0.1.8-py3-none-any.whl
Size 13.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
46f0681b177de7fc870a178ec07c93baa3cb5e181cb685bafccbafd6e885f148
BLAKE2b-256 checksum
How to use checksums
1ba4d404c2f1f8389364a4c6f03f5c3fbe350b74d0c1b09d02617849c3360bcb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.8 This release

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

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