OpenAI leads in model capability. But capability without per-inference evidence is a compliance gap your auditor will find.
v0.7.9 RELEASED – OpenAI adapter with streaming and system fingerprint witnessing. Available on PyPI and npm.
Who this is for: Developers integrating GPT-4o, o1, and o3 models, compliance leads managing AI deployments, security architects designing inference pipelines, and auditors evaluating OpenAI-powered applications.
OpenAI publishes model cards, safety evaluations, and a Preparedness Framework. These assess the model. They do not assess your deployment. When GPT-4o processes a customer query in your application, what evidence proves that specific call was governed? OpenAI can tell you the model is safe. Only you can prove your use of it is compliant.
OpenAI provides significant safety infrastructure. Model cards document capabilities and limitations. The Preparedness Framework evaluates catastrophic risk categories. Usage policies restrict dangerous applications. System fingerprints identify specific model snapshots. This is necessary. It is not sufficient for your compliance obligations.
The gap is between model-level assurance and deployment-level evidence:
| Evidence Need | What OpenAI Provides | What You Need | Gap |
|---|---|---|---|
| Model safety | Model cards, safety evals | Per-call proof of governed inference | Model != deployment |
| Model version | system_fingerprint |
Anchored version pin tied to each decision | Fingerprint alone proves version, not governance |
| Content filtering | Moderation endpoint, refusal behavior | Per-call refusal evidence with timestamp | No persistent refusal audit trail |
| Usage logging | API dashboard, usage stats | Cryptographic decision record per inference | Aggregate stats, not per-decision evidence |
| System prompt integrity | None | Hash of system instructions per call | No system prompt tracking at all |
The system_fingerprint deserves special attention. It is genuinely useful -- it identifies the exact model snapshot that served a request. But it only proves model version. It does not prove what your system prompt said, what the model returned, whether the response was appropriate for your use case, or that governance was applied. SWT3 captures the fingerprint as one component of a broader evidence record.
The SWT3 adapter wraps the OpenAI client transparently. Every call to chat.completions.create() produces a witness anchor in the background. The response is untouched. Your application code does not change.
from openai import OpenAI
from swt3_ai import Witness
witness = Witness(
endpoint="https://your-instance.example.com/api/v1/witness",
api_key="axm_live_...",
tenant_id="YOUR_TENANT",
)
client = witness.wrap(OpenAI())
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a financial advisor."},
{"role": "user", "content": "Analyze this portfolio."},
],
)
# response is untouched -- witnessing happens in the background
import OpenAI from 'openai';
import { Witness } from '@tenova/swt3-ai';
const witness = new Witness({
endpoint: 'https://your-instance.example.com/api/v1/witness',
apiKey: 'axm_live_...',
tenantId: 'YOUR_TENANT',
});
const client = witness.wrap(new OpenAI());
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: 'You are a financial advisor.' },
{ role: 'user', content: 'Analyze this portfolio.' },
],
});
// response is untouched -- witnessing happens in the background
model_id: gpt-4o -- the model name as requested.
model_hash: Composite hash including the system_fingerprint from the response (when available). This pins the exact model snapshot that served the request, not just the alias.
prompt_hash: SHA-256 of the full messages array. Proves what was sent without storing the content.
response_hash: SHA-256 of the completion text. Proves what was returned.
token_usage: prompt_tokens, completion_tokens, total_tokens from the response.
latency_ms: Wall-clock time for the API call.
The anchor includes a hash of OpenAI's system_fingerprint, which changes when OpenAI updates the model snapshot behind an alias like "gpt-4o". Compare model_hash values across audit periods. If the hash changes without a corresponding model change ticket in the organization's change management system, it indicates an unplanned model rotation -- either OpenAI updated the snapshot or the organization switched models.
Streaming responses present a unique challenge: the response arrives as a sequence of chunks, not a single payload. The adapter handles this transparently by accumulating chunks and computing the response hash on the complete assembled text.
from openai import OpenAI
from swt3_ai import Witness
witness = Witness(
endpoint="https://your-instance.example.com/api/v1/witness",
api_key="axm_live_...",
tenant_id="YOUR_TENANT",
)
client = witness.wrap(OpenAI())
stream = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a medical coding assistant."},
{"role": "user", "content": "Classify this diagnosis."},
],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
# Anchor minted after stream completes with full response hash
import OpenAI from 'openai';
import { Witness } from '@tenova/swt3-ai';
const witness = new Witness({
endpoint: 'https://your-instance.example.com/api/v1/witness',
apiKey: 'axm_live_...',
tenantId: 'YOUR_TENANT',
});
const client = witness.wrap(new OpenAI());
const stream = await client.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: 'You are a medical coding assistant.' },
{ role: 'user', content: 'Classify this diagnosis.' },
],
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of stream) {
const content = chunk.choices?.[0]?.delta?.content;
if (content) process.stdout.write(content);
}
// Anchor minted after stream completes with full response hash
Use stream_options: {"include_usage": true} to receive token counts in the final stream chunk. Without this option, the anchor will still be minted but token_usage will be absent. The response hash and all other evidence fields are unaffected.
The adapter accumulates all delta.content chunks into a single string and hashes the complete text. A streamed response produces the same response_hash as an identical non-streamed response. This guarantees hash consistency regardless of delivery method.
Streaming and non-streaming calls for identical inputs should produce identical response_hash values (assuming the same model snapshot). If an organization uses both modes, compare hashes across a sample set. Divergence indicates either a model rotation between calls or content post-processing outside the witness boundary.
OpenAI uses the messages array for system instructions. One or more messages with role: "system" define the model's behavior for that call. These instructions are the most governance-critical text in your application -- they determine what the model will and will not do.
Yet system prompts have no audit trail in the OpenAI platform. There is no log of what system prompt was active on a given date. No evidence that it matched your approved configuration. No detection when someone modifies it.
The adapter extracts all role: "system" messages from the messages array and hashes them independently. This creates a system_prompt_hash that travels with every anchor. If the system prompt changes between calls -- intentionally or not -- the hash changes, creating an automatic drift detection signal.
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a loan underwriting assistant. "
"Never approve loans exceeding $500,000 without escalation."},
{"role": "user", "content": "Review application #4471."},
],
)
# system_prompt_hash captures the exact instructions active for this call
The system_prompt_hash is computed from the concatenation of all system messages in the messages array. It is independent of the prompt_hash (which covers the full messages array including user messages).
Request a time-series of system_prompt_hash values for a given application over the assessment period. A stable hash means the system prompt never changed. A hash change is not inherently a finding -- it may reflect an approved update. Cross-reference each change against the organization's change management log. An unexplained hash change is a configuration integrity finding. Multiple distinct hashes active simultaneously across different instances may indicate inconsistent deployment.
OpenAI model names are aliases. gpt-4o does not refer to a fixed model -- it resolves to the latest production snapshot, which OpenAI updates periodically. The system_fingerprint field in the API response identifies the specific snapshot that served a request.
The adapter captures model version evidence at two levels:
gpt-4o)system_fingerprint from the response, when available. This pins the exact snapshot, not just the alias.If you use a date-pinned model name like gpt-4o-2024-11-20, the model_id reflects the pinned name. The model_hash still incorporates system_fingerprint for additional precision -- even pinned models can receive minor infrastructure updates that change the fingerprint.
# Using an alias -- model_hash will change when OpenAI rotates the snapshot
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Summarize this contract."}],
)
# Using a pinned version -- model_hash is more stable but still tracks fingerprint
response = client.chat.completions.create(
model="gpt-4o-2024-11-20",
messages=[{"role": "user", "content": "Summarize this contract."}],
)
The anchor records both what you asked for (model_id) and what you got (model_hash incorporating system_fingerprint). This distinction matters because OpenAI can change what "gpt-4o" resolves to at any time.
Compare model_hash values across audit periods for anchors with the same model_id. A change in model_hash while model_id remains constant means the underlying model snapshot changed. For regulated workloads, the organization should have a policy on acceptable model rotation frequency. If the organization uses aliased model names and the hash changes more frequently than their policy allows, recommend switching to date-pinned model names for production workloads.
OpenAI models can refuse requests through multiple mechanisms. The adapter detects each one and records it in the anchor:
message.refusal field: When the model explicitly refuses a request, the API response includes a refusal field on the message object containing the refusal explanation. The adapter sets has_refusal=true.finish_reason="content_filter": When OpenAI's content moderation system blocks the response, the finish_reason changes from stop to content_filter. The adapter detects this and sets has_refusal=true.Both mechanisms produce the same evidence signal: a witness anchor with has_refusal=true. The distinction between model-initiated refusal and platform-initiated filtering is preserved in the response_hash (different content produces different hashes), but the governance signal is unified.
Every refusal produces a witness anchor. This is evidence that guardrails are active and functioning. The absence of refusal anchors over a long period in a production system is itself a finding -- it may indicate the system prompt is too permissive or the model is not encountering edge cases.
Query the ledger for has_refusal=true anchors over the assessment period. Verify: (1) refusals exist in a production system with user-facing inputs, (2) refusal density is plausible for the use case (a customer-facing chatbot with zero refusals over 90 days warrants investigation), (3) no refusal anchors are followed by successful re-attempts with the same prompt_hash in rapid succession (which may indicate prompt injection attempts or guardrail bypass efforts).
Every anchor minted on an OpenAI call maps to specific regulatory obligations. The evidence is concrete -- each anchor is the artifact that satisfies the requirement.
| Obligation | Regulation | SWT3 Evidence | Procedure |
|---|---|---|---|
| Automatic logging of AI operation | EU AI Act Art. 26 | Per-inference anchor with prompt/response hash, model ID, timestamp | AI-INF.1 |
| Risk management documentation | NIST AI RMF MN 2.2 | Continuous model behavior tracking via hash comparison and refusal rates | AI-INF.1, AI-GRD.1 |
| AI system transparency | EU AI Act Art. 13 | Model version pinning (system_fingerprint in model_hash), system prompt hash | AI-INF.2 |
| Model inventory and change management | ISO 42001 A.6.2.4 | model_hash time-series detects model rotation automatically | AI-INF.1 |
| Human oversight measures | EU AI Act Art. 14 | Refusal detection evidence (has_refusal flag), guardrail intervention records | AI-GRD.1 |
| Ongoing monitoring | NIST AI RMF MAP 1.5 | Anchor density, system_prompt_hash drift, model_hash changes over time | AI-INF.1, AI-INF.2 |
| Model governance alignment | OpenAI Preparedness Framework | Per-deployment evidence that complements OpenAI's model-level evaluations | AI-INF.1 |
OpenAI's safety work and SWT3 witnessing are complementary, not competing. OpenAI evaluates whether GPT-4o is safe to deploy. SWT3 proves that your deployment of GPT-4o is governed. Regulators expect both: model-level assurance from the provider and deployment-level evidence from you.
With SWT3 witnessing active, every GPT-4o call in your application now has a cryptographic anchor that proves which model version served the request, what system prompt was active, whether content filtering intervened, and how long the inference took -- without storing any content. Your next audit starts with evidence, not with log reconstruction.
# Python
pip install swt3-ai openai
# TypeScript
npm install @tenova/swt3-ai openai
from openai import OpenAI
from swt3_ai import Witness
witness = Witness(endpoint="https://your-instance.example.com/api/v1/witness", api_key="axm_live_...", tenant_id="YOUR_TENANT")
client = witness.wrap(OpenAI())
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello, world."}],
)
witness.flush()
Three lines: create the witness, wrap the client, flush when done. Every chat.completions.create() call is now witnessed. The response object, streaming behavior, error handling, and retry logic are all unchanged.