Verifying LLM Identity via API: A Behavioral Fingerprinting Approach

    When your team consumes large language models through third-party APIs, how can you verify that the backend actually runs the contracted model? This article presents a practical black-box monitoring method based on behavioral fingerprints—entropy statistics, semantic embedding positions, and watermark probing—along with probe set design, boundary conditions, and a complete Python implementation for feature extraction and similarity comparison.

    1. Background and Scope

    Many teams consume large language models through third-party APIs. The model version promised in the contract may differ from what the backend actually executes. Some providers claim to serve a top-tier closed-source model while actually routing requests to a smaller open-source model or a fine-tuned variant. Others quietly switch to cheaper versions during peak hours.

    API consumers have no access to weight files, so hash comparison and structural metadata are unavailable. Judgment must rely solely on model behavior—the relationship between inputs and outputs. This approach has three clear boundaries:

    • The result is evidence of consistency, not absolute proof. Minor version upgrades, quantized deployments, or system prompt changes by the same vendor can all alter output characteristics.
    • Fine-tuned variants often behave similarly to the base model on many probes and may be judged as a match.
    • Conclusions depend on the probe set. Changing the question bank can change the verdict.

    For this reason, reports should state “high likelihood of consistency with the baseline” or “deviation from baseline, manual review required,” rather than asserting a specific model has been confirmed.

    2. Three Signal Types and Their Applicability

    Signal API requirement Cost Main false-positive risk Applicable scenarios
    Output entropy and probability distribution Endpoint returns logprobs or top_logprobs Low, computed locally Temperature, top_p, prompts, and length changes all alter entropy APIs exposing token probabilities
    Semantic embedding position Only response text needed Medium, requires an embedding model Stylistically similar models are hard to distinguish; prompt template changes have large impact Most black-box APIs
    Watermark detection Provider publishes the watermark scheme and cooperates with detection High, depends on cooperation Unusable when the scheme is undisclosed; rewriting and translation weaken watermarks Scenarios with signed cooperation agreements

    Output entropy. Aligned models typically produce more concentrated outputs with lower entropy. However, this rule only holds when conditions are identical. Fix temperature, top_p, system prompts, and maximum output length, and normalize for response length. Otherwise, raising the temperature by one notch may shift entropy more than the difference between models.

    Semantic embedding position. Convert responses to fixed probes into vectors and compare their positions in the embedding space. It depends only on text, so it applies broadly. But it is sensitive to prompt templates—use exactly the same template for every test run.

    Watermark detection. Watermark verification requires provider cooperation and a disclosable watermark scheme. If the provider does not publish the detection method, the verifier cannot construct effective trigger probes. It works best as supplementary verification between cooperating parties, not as independent evidence against an external provider.

    3. Probe Set Design

    Probes are the foundation of verification. The following parameters are recommended:

    • Task types: reasoning, common sense, math, code, Chinese writing, and instruction following—six categories in total.
    • Volume: 40 questions per category, roughly 240 questions overall. Use stratified sampling by category for each session, drawing 15 questions per category, about 90 questions per run.
    • Repetitions: send each probe 3 times and average the results to reduce sampling variance.
    • Confidentiality: keep the question bank private, store questions and reference answers separately, and log access. Rotate the bank periodically to reduce the risk of targeted optimization.
    • Verdict records: for each session, save the timestamp, the claimed model name, probe IDs, response text hashes, entropy statistics, embedding vectors, similarity scores, and conclusions.

    4. Feature Extraction Code

    The following code demonstrates the full pipeline: fixing sampling parameters, requesting logprobs, computing entropy statistics, extracting embedding vectors, and calculating similarity. The API URL and key are passed via environment variables rather than hardcoded. Python 3.10 or higher is required.

    1. import math
    2. import os
    3. import numpy as np
    4. import requests
    5. from sentence_transformers import SentenceTransformer
    6. API_URL = os.environ["PROBE_API_URL"] # OpenAI-compatible chat endpoint
    7. API_KEY = os.environ["PROBE_API_KEY"]
    8. MODEL = os.environ["PROBE_MODEL"] # The model name claimed by the provider
    9. TIMEOUT = 60
    10. embedder = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") # Fixed once chosen
    11. def ask(prompt: str) -> dict:
    12. """Send one probe with fixed sampling parameters and request logprobs."""
    13. payload = {
    14. "model": MODEL,
    15. "messages": [{"role": "user", "content": prompt}],
    16. "temperature": 0,
    17. "top_p": 1,
    18. "max_tokens": 256,
    19. "logprobs": True,
    20. "top_logprobs": 5,
    21. }
    22. headers = {"Authorization": f"Bearer {API_KEY}"}
    23. resp = requests.post(API_URL, json=payload, headers=headers, timeout=TIMEOUT)
    24. resp.raise_for_status()
    25. return resp.json()["choices"][0]
    26. def entropy_features(choice: dict) -> dict | None:
    27. """Compute mean entropy, top-1 token probability, and related statistics. Returns None if logprobs are unavailable."""
    28. steps = (choice.get("logprobs") or {}).get("content")
    29. if not steps:
    30. return None
    31. entropies, top1, chosen = [], [], []
    32. for step in steps:
    33. tops = step.get("top_logprobs") or []
    34. if not tops:
    35. continue
    36. probs = np.array([math.exp(t["logprob"]) for t in tops])
    37. probs = probs / probs.sum() # Normalized only within top-k, so entropy is approximate
    38. entropies.append(float(-(probs * np.log(probs + 1e-12)).sum()))
    39. top1.append(float(probs.max()))
    40. chosen.append(math.exp(step["logprob"]))
    41. if not entropies:
    42. return None
    43. return {
    44. "mean_entropy": float(np.mean(entropies)),
    45. "mean_top1_prob": float(np.mean(top1)),
    46. "mean_chosen_prob": float(np.mean(chosen)),
    47. "n_tokens": len(entropies),
    48. }
    49. def run_session(probes: list[str], repeats: int = 3) -> dict:
    50. """Run one session over all probes and return the feature vector and entropy statistics."""
    51. texts, feats = [], []
    52. for prompt in probes:
    53. for _ in range(repeats):
    54. choice = ask(prompt)
    55. if choice.get("finish_reason") != "stop":
    56. continue # Discard responses truncated by length or ended abnormally
    57. texts.append(choice["message"]["content"])
    58. f = entropy_features(choice)
    59. if f is not None:
    60. feats.append(f)
    61. vector = embedder.encode(texts, normalize_embeddings=True).mean(axis=0)
    62. entropy = None
    63. if feats:
    64. entropy = {k: float(np.mean([f[k] for f in feats]))
    65. for k in ("mean_entropy", "mean_top1_prob", "mean_chosen_prob")}
    66. return {"vector": vector, "entropy": entropy, "n_texts": len(texts)}
    67. def cosine(a: np.ndarray, b: np.ndarray) -> float:
    68. """Compute cosine similarity between two vectors."""
    69. return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))

    The cosine function compares the current session’s embedding vector against a stored baseline vector. Combined with the entropy statistics, it provides the evidence needed to flag sessions for manual review when outputs drift from the baseline.