Skip to main content

prompt-portability

CI

Lint LLM chat-completion prompts for cross-provider portability issues.

A prompt authored and tuned against one provider's API often breaks -- silently, or with a rejected request -- when the same request body is sent to another. prompt-portability reads OpenAI- or Anthropic-shaped request JSON and flags the assumptions that don't travel: system-prompt placement, provider-specific limits (stop-sequence counts, temperature ranges), leaked chat-template tokens from a different model family, deprecated/nonstandard message roles, and JSON-mode footguns.

Quickstart

pip install prompt-portability

prompt-portability check prompts/*.json

Exits non-zero if any finding is error-severity, so it's usable as a CI gate. Add --json for machine-readable output.

check only detects issues. To resolve the ones that have an unambiguous, lossless rewrite -- rather than requiring a human judgment call -- run:

prompt-portability fix prompts/*.json

fix rewrites the file(s) in place and prints what changed, followed by whatever findings remain (still exits non-zero if any of those are error-severity). Add --dry-run to preview without writing.

Suppressing a rule

A known, intentional case (e.g. a deliberately high temperature) doesn't have to fail CI. Suppress it per-invocation:

prompt-portability check prompts/*.json --ignore temperature-range

or check it in so the whole team gets it, via a .prompt-portability.json file (looked up in the current directory by default; --config to point elsewhere) next to where you run the CLI:

{"ignore": ["temperature-range"]}

--ignore is repeatable and stacks with the config file. A bare family name (e.g. "system-prompt") suppresses every rule in that family (system-prompt/empty, system-prompt/multiple, etc). Suppression isn't embedded in the request JSON itself, since those files are meant to double as real API request bodies -- an extra top-level key risks a provider that rejects unknown fields.

What it checks (v0.1.0)

Rule Severity Catches fix?
system-prompt/not-first error A system-role message appears mid-conversation instead of first/dedicated yes -- merged into the leading/dedicated system field
system-prompt/multiple warning More than one inline system-role message yes -- merged (nothing dropped)
system-prompt/empty warning A present-but-blank system prompt yes -- removed
stop-sequences error More than 4 stop sequences (OpenAI's hard limit) yes -- truncated to 4, order-preserved
temperature-range warning temperature > 1.0 (valid on OpenAI's 0-2 scale, out of range elsewhere) no -- clamping changes sampling behavior
hardcoded-provider-reference warning Prompt text says "you are ChatGPT/Claude/Gemini" etc. no -- rewriting authored text is a content decision
leaked-special-tokens error Literal <|im_start|>, [INST], <<SYS>>, <start_of_turn> in message content yes -- stripped
unsupported-role/legacy-function warning OpenAI's deprecated function role instead of tool partial -- renamed, but you must add a tool_call_id by hand
unsupported-role error A message role outside {user, assistant, tool} no -- no way to infer the intended portable role
json-mode-missing-hint warning response_format: json_object with no message mentioning "json" no -- injecting text changes the actual instructions

fix only touches the rows marked yes/partial above -- see Auto-fixing for why the rest are left to a human.

Input format

Point check at JSON files shaped like an OpenAI Chat Completions request body, an Anthropic Messages API request body, or a Google Gemini generateContent request body -- the format is auto-detected per file (llm_prompt_lint.parsers.detect_and_parse). All three shapes are normalized to one provider-agnostic model before rules run, so every rule applies to all three.

{
  "messages": [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Hi"}
  ],
  "temperature": 0.7,
  "stop": ["END"]
}

Auto-fixing (fix)

Only findings with an unambiguous, lossless rewrite are auto-fixed -- deliberately not all 10 rules. Merging duplicate system messages, dropping an empty one, stripping a leaked template token, and truncating excess stop sequences are mechanical: nothing about the fix depends on guessing what the prompt author meant. Renaming OpenAI's legacy function role to tool is applied but flagged for manual follow-up, since a valid tool message also needs a tool_call_id that can't be inferred.

The other three rules are left alone on purpose: clamping temperature changes actual sampling behavior, rewriting "you are ChatGPT" requires knowing what the author intended instead, and injecting the word "json" to satisfy OpenAI's json-mode requirement changes the model's actual instructions. Those are content decisions, not syntax fixes -- fix reports them as remaining issues rather than guessing.

Library API

from llm_prompt_lint.parsers import detect_and_parse
from llm_prompt_lint.linter import lint
from llm_prompt_lint.fixers import apply_fixes

fixes = apply_fixes(request_body, path="prompts/greet.json")  # mutates in place

doc = detect_and_parse(request_body, source_path="prompts/greet.json")
report = lint(doc)
print(report.to_table())

Development

pip install -e ".[dev]"
pytest
ruff check .
mypy

Status

v0.1.0. OpenAI + Anthropic + Gemini request parsers; 10 portability rules; check (detect) and fix (auto-fix the 4-and-a-half safely-fixable ones, --dry-run supported); per-rule suppression (--ignore, .prompt-portability.json); JSON/table CLI output; CI across 3 OSes x 3 Python versions plus a lint/type-check job. 86 tests passing.

Known gap: rules operate on a single request snapshot, not a stored prompt template with variable substitution.

License

MIT

Metadata

Release files for prompt-portability 0.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 prompt-portability 0.1.0
File Size Uploaded Provenance Actions
prompt_portability-0.1.0.tar.gz 20.9 kB Without provenance Details for prompt_portability-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for prompt-portability 0.1.0
File Interpreter ABI Platform Actions
prompt_portability-0.1.0-py3-none-any.whl Python 3 none any Details for prompt_portability-0.1.0-py3-none-any.whl

Total release size: 45.3 kB

Release files / prompt_portability-0.1.0.tar.gz

Download URL prompt_portability-0.1.0.tar.gz
Size 20.9 kB
Tags Source
SHA-256 checksum
How to use checksums
65d15ffdcc2b07ef6920b9827da013df481af855a04c2cf83cc5e8e234bc8cf0
BLAKE2b-256 checksum
How to use checksums
7a699628918068f920e91dfe5dd98aec884e954dd6a0d0ded3876c3fa4e566a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / prompt_portability-0.1.0-py3-none-any.whl

Download URL prompt_portability-0.1.0-py3-none-any.whl
Size 24.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
05408ff6252ac5918ca243b6428d75b380952daf739020144ee7a5f0fa93b5fd
BLAKE2b-256 checksum
How to use checksums
36c7755b6ea28053893e08eb2975783bdb392854f0f139650426f0dd60592c23
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for prompt-portability 0.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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

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