Testing Conversation Preservation Across LLM APIs
API gateways, proxies, and SDK adapters let applications switch between OpenAI, Anthropic, and Gemini—but a translated request or response can pass schema validation (HTTP 200) while silently dropping prompts, stripping tool error flags, masking stop reasons, or injecting parameters. ConvPreserve compares payloads before and after translation to catch silent semantic changes offline, with no API keys, no LLM judge, and no reference outputs required.
Supported APIs: OpenAI Chat Completions · OpenAI Responses · Anthropic Messages · Google Gemini
Covers: Requests · Responses · Streaming (SSE)
Source RequestAnthropic Messages
Translated RequestGoogle Gemini
Schema validation
Valid (HTTP 200)
Accepted by the Gemini API without error
ConvPreserve
Silent Loss (T1)
System prompt
"Reply in JSON." dropped during translation
Getting Started
1. Install & compare two payloads
$ pip install git+https://github.com/convpreserve/conv-preserve.git $ convpreserve check --side request \ --src-dialect openai --dst-dialect anthropic \ before.json after.json S08 T1 unlicensed control thinking injected dst:/thinking
Python 3.11+ · zero dependencies · exits with 0 when preserved or 1 on silent loss.
2. Or test a running local gateway
# Point your gateway's upstream URL at http://127.0.0.1:18600 $ convpreserve probe --gateway http://127.0.0.1:8080 --target openai
Starts a local mock upstream server and sends 51 built-in test requests—no API keys needed.
Documentation
Finding Categories
| Tier | Rules | Classification | What It Checks | Default Exit |
|---|---|---|---|---|
| T1 | S01–S13 | Silent semantic loss | Avoidable changes to prompts, message roles, tool schemas, tool calls or error flags, sampling/reasoning parameters, or stop reasons. | exit 1 |
| T2 | D01–D06 | Target API limitation | Target API lacks a direct equivalent (e.g., system merged into user message), translator fills in a default parameter, or cache/usage metadata changes. |
exit 0 |
| T3 | R01–R08 | Equivalent format difference | Different JSON structure with identical conversation meaning, such as empty string vs. null or merged adjacent text blocks. | exit 0 |
CLI Reference
| Command | Arguments | Purpose |
|---|---|---|
| convpreserve check | --side {request,response,stream} --src-dialect FMT --dst-dialect FMT BEFORE AFTER | Compare a payload before and after translation (formats: openai | responses | anthropic | gemini; pass --json for JSON output). |
| convpreserve batch | MANIFEST.jsonl [--out FILE] [--summary-md FILE] | Check a batch of payload pairs listed in a .jsonl file for regression suites and CI pipelines. |
| convpreserve probe | --gateway URL --target {openai,responses,gemini,anthropic} | Test a running local gateway end-to-end: starts a local mock upstream server (127.0.0.1:18600), sends 51 built-in test requests, and checks every forwarded payload. |
| convpreserve explain | RULE_ID | --list | Show what a rule checks and known bug patterns in open-source translators (for example, convpreserve explain S08). |
Examples
Source RequestAnthropic Messages
Translated RequestOpenAI Chat
S03 · T1
Evaluation
76.0%
recall · 2.0% false-alarm rate
Catches 95 of 125 injected translation bugs on held-out tests, with only 2 false alarms across 100 valid format conversions.
18.5%
127 / 685 translated requests
Requests across 5 popular translators that pass schema validation yet silently alter prompts, roles, or tools.
36.4%
203 / 558 translated streams
Translated SSE streams that crash or silently corrupt messages when parsed by an official OpenAI SDK.
7 fixed
26 bugs reported · 12 projects
Confirmed translation bugs reported to open-source gateway and SDK maintainers, with 7 fixes already merged.
Paper
Testing Conversation Preservation Across LLM APIs: A Rule-Based Oracle for Detecting Silent Information Loss
Code, test suites, and bundled examples: github.com/convpreserve/conv-preserve