Python SDKPython SDK

Python SDK

Source: rahvis/cci-sdk. Package cci-sdk, module cli_sdk, Python 3.9 or later. Dependencies: httpx and numpy.

Install

pip install cci-sdk

Everything below is importable from the top-level package:

from cli_sdk import (
    CLIClient, AsyncCLIClient, RetryConfig,                             # clients
    Belief, Set, Interval, Gate, Claim, Judge, Route,                   # queries
    BeliefAnswer, SetAnswer, IntervalAnswer, GateAnswer,                # answers
    ClaimAnswer, JudgeAnswer, RouteAnswer, EvaluateResponse, Guarantee, Usage,
    CalibrationProfile, CalibrationExample, LocalMonitor, Alert,        # calibration, monitoring
    OpenAIBackend, AzureOpenAIBackend, AnthropicBackend, GeminiBackend, # backends
    BedrockBackend, OpenRouterBackend, VLLMBackend, SGLangBackend, CustomBackend,
    CLIError, AuthenticationError, ValidationError,                     # errors
    InsufficientCalibrationError, RateLimitError, BackendError, ConfigurationError,
)

The HTTP clients are imported lazily, so import cli_sdk and the offline engine cli_sdk.stats never load the HTTP stack.

Clients

from cli_sdk import Belief, CLIClient, OpenAIBackend
 
with CLIClient(backend=OpenAIBackend(model="gpt-4.1-2025-04-14")) as client:   # default backend
    result = client.evaluate(
        context={"message": "Help! My payouts have been failing for 3 days."},
        queries={"urgent": Belief(instructions="Does this message convey urgency?",
                                  calibration_profile="urgency-v1")},
    )

CLIClient and AsyncCLIClient take the same arguments:

ArgumentDefaultMeaning
api_keyCLI_API_KEYBearer token. Required unless base_url is on localhost, 127.0.0.1, 0.0.0.0 or ::1; otherwise a missing key raises ConfigurationError.
base_urlCLI_BASE_URL, else https://cci.gitdate.ink/api/v1API root.
backendNoneDefault backend for evaluate.
timeout60.0Seconds, passed to the httpx client.
retryRetryConfig()Retry policy; see Errors and retries.
strict_guaranteesFalseRaise InsufficientCalibrationError instead of returning heuristic answers.
sample_count20Samples requested from a CustomBackend at L0.
default_headersNoneExtra headers on every request.
http_clientNoneYour own httpx.Client / httpx.AsyncClient (proxies, custom TLS, httpx.MockTransport in tests). The SDK does not close a client you pass in.
MethodReturns
evaluate(context, queries, backend=None, response_model=None)EvaluateResponse
list_backends()list[dict]
calibration_profilesProfile, audit, and monitor operations (Calibration profiles)
close()Closes the HTTP client; called for you by with / async with

On AsyncCLIClient, evaluate, list_backends, close, and every calibration_profiles method are coroutines.

Queries

ClassRequiredOptional
Beliefinstructions, calibration_profilecriteria
Setinstructions, options (2 or more), calibration_profilealpha, method, group_by, backend_access_hint
Intervalinstructions, levels (2 to 10), calibration_profilealpha, method
Gateinstructions, calibration_profile, targetguarantee (default "risk"), delta, loss
Claiminstructions, calibration_profilealpha, support_source
Judgeinstructions, calibration_profilealpha, cascade
Routecascade, calibration_profileguarantee (default "cost_budget"), target_cents, alpha

Queries are dataclasses that validate in __post_init__ and raise ValueError on a missing or invalid field. query.to_payload() returns the exact JSON object sent on the wire.

Responses

result = client.evaluate(...)
result.answers          # dict: query id -> typed answer
result.usage            # Usage(backend_calls=..., backend_tokens=...)
result.request_id       # from the x-request-id header
result.warnings         # list[str]
result.backend          # dict
result.heuristic_answers  # ids of answers that carry no formal guarantee

Every answer is a frozen dataclass with guarantee (a Guarantee), raw (the untouched payload), and is_heuristic, plus its own fields:

AnswerFields and helpers
BeliefAnswerprobability, venn_abers, interval_width, straddles(threshold)
SetAnswerset, probabilities, venn_abers, is_singleton, is_empty, top
IntervalAnswerpoint_estimate, interval, legend, width, contains(value)
GateAnswerdecision, approved
ClaimAnswerretained_claims, dropped_claims, retention_rate, as_text(separator=" ")
JudgeAnswerwinner, escalated_to, needs_human
RouteAnswerserved_by, escalated, cost_cents, output

Typed responses

Subclass EvaluateResponse and annotate query ids with their answer types to get attribute access, checked when the response is parsed:

from cli_sdk import CLIClient, EvaluateResponse, GateAnswer, Gate, OpenAIBackend, Set, SetAnswer
 
class RoutingResponse(EvaluateResponse):
    department: SetAnswer
    route: GateAnswer
 
with CLIClient() as client:
    result = client.evaluate(
        context={"ticket": "I was charged twice."},
        backend=OpenAIBackend(model="gpt-4.1-2025-04-14"),
        queries={
            "department": Set(
                instructions="Which team should handle this ticket?",
                options={"billing": "Payments, invoicing, refunds",
                         "technical": "Bugs, outages, integrations"},
                calibration_profile="support-routing-v3",
            ),
            "route": Gate(
                instructions="Auto-route this ticket without human review?",
                calibration_profile="support-routing-v3",
                guarantee="fdr",
                target=0.05,
            ),
        },
        response_model=RoutingResponse,
    )
 
assert result.department is result.answers["department"]

A declared answer that is missing raises ValueError; one of the wrong type raises TypeError; a response_model that is not an EvaluateResponse subclass raises ConfigurationError.

Backends

Typed backend classes (OpenAIBackend, AzureOpenAIBackend, AnthropicBackend, GeminiBackend, BedrockBackend, OpenRouterBackend, VLLMBackend, SGLangBackend) or plain dicts with a "provider" key are accepted anywhere a backend is. Their fields are on Backends.

Bring your own model

from cli_sdk import CLIClient, CustomBackend
 
class MyEngine(CustomBackend):
    access_level = "L3"
 
    def score_options(self, context, instructions, options):
        return my_model.option_probabilities(context, instructions, list(options))
 
    def sample(self, context, instructions, n):
        return [my_model.generate(context, instructions) for _ in range(n)]
 
    def score_text(self, context, instructions, candidate):
        return my_model.support_score(context, instructions, candidate)
 
client = CLIClient(backend=MyEngine(base_url="http://localhost:8000"))

Implement only the methods your access level supports. The SDK calls them locally and sends only the evidence, with backend provider "client_evidence". The full method table, and which queries use which method, is on Backends: bring your own model.

Calibration profiles and monitors

client.calibration_profiles.create(name, backend, method="APS", alpha=0.10,
                                   group_by=None, prompt_template_hash=None,
                                   strict_fingerprint=True)        # -> CalibrationProfile
client.calibration_profiles.get(name)                              # -> CalibrationProfile
client.calibration_profiles.list()                                 # -> list[CalibrationProfile]
client.calibration_profiles.add_examples(name, examples)           # -> CalibrationProfile
client.calibration_profiles.label_with_judge(name, judge, unlabelled_examples,
                                             human_labelled_sample_size=300)  # -> dict
client.calibration_profiles.audit(name, fresh_examples)            # -> AuditResult
 
client.calibration_profiles.monitors.create(profile, type="coverage", target=None,
                                            false_alarm_rate=0.05,
                                            labelled_sample_rate=None,
                                            use_judge_pseudo_labels=False)   # -> Monitor
client.calibration_profiles.monitors.list(profile)                 # -> list[Monitor]
client.calibration_profiles.monitors.poll(profile, monitor_id=None)  # -> iterator of Alert

Details: Calibration profiles, Drift monitoring. Network-free helpers live in cli_sdk.calibration (audit_coverage, audit_risk, clopper_pearson, judge_quality, estimate_rate_with_judge, recommended_size) and cli_sdk.LocalMonitor.

Errors and retries

429, 502, and 529 are retried with full-jitter exponential backoff, honoring retry-after:

from cli_sdk import CLIClient, RetryConfig
 
client = CLIClient(retry=RetryConfig(max_attempts=5, base_delay=0.5, max_delay=8.0))

The full exception map, the 424 heuristic behavior, and strict_guarantees are on Errors and retries.

Package layout

src/cli_sdk/
  client/          sync and async clients, retry policy
  queries/         Belief, Set, Interval, Gate, Claim, Judge, Route
  answers/         typed answers, the Guarantee card, EvaluateResponse
  calibration/     profiles, examples, audits, label-efficient calibration
  monitoring/      hosted monitors, alerts, LocalMonitor
  backends/        one adapter per provider, plus CustomBackend
  stats/           offline engine: conformal/, venn_abers/, evalues/
  cli_tool.py      the `cli` command
  exceptions.py    CLIError and subclasses
  constants.py     base URL, environment variable names, guarantee types, access levels