Makes any LLM behave like a regular REST API. Define the contract, specllm enforces it.
Project description
specllm
Makes any LLM behave like a regular REST API. Define the contract, specllm enforces it. The caller never knows an LLM is involved.
Every team shipping LLMs into production rebuilds the same layer: validation, retries, caching, error handling. specllm handles all of it from your API spec.
flowchart LR
A[Your App] -->|Request JSON| B[specllm]
B -->|Prompt| C[Any LLM]
C -->|Response| B
B -->|Validated JSON| A
Quick Start
pip install specllm
python -m specllm.demo # runs locally, no API key needed
Or with a real LLM:
pip install specllm[anthropic]
from specllm import SpecLLM
app = SpecLLM.from_openapi("./api.yaml") # provider inferred from spec
app.serve(port=8080)
Your spec declares the model:
info:
x-specllm-model: claude-sonnet-4-20250514
That's it. No provider class, no config.
Features
Model Selection via Spec
info:
x-specllm-model: claude-sonnet-4-20250514 # default for all endpoints
paths:
/v1/fast-route:
post:
x-specllm-model: claude-haiku-4 # override: cheap/fast
Built-in providers: Anthropic (claude-*) and OpenAI (gpt-*, o1-*, o3-*). Custom providers just implement one method:
class MyProvider(LLMProvider):
def call(self, prompt: str, system_prompt=None) -> str:
... # return raw text or dict — pipeline handles JSON extraction
Dynamic Response Constraints (x-constrain-from)
Let the caller control what the LLM can return — per request:
intent:
type: string
x-constrain-from: "intents" # enum pulled from request body field "intents"
score:
type: integer
x-constrain-from:
minimum: "min_score" # range pulled from request body
maximum: "max_score"
If the LLM picks outside the caller's options, specllm retries with feedback automatically.
Custom Validation & Prompts
@app.validate("/v1/route-ticket")
def only_enterprise(body):
if body.get("customer_tier") != "enterprise":
return "Only enterprise tickets accepted"
@app.prompt("/v1/answer")
def grounded_prompt(body):
return (
f"Answer ONLY from the following context. If the answer is not in the context, say so.\n\n"
f"Context:\n{body['context']}\n\n"
f"Question: {body['question']}"
)
Configuration (optional overrides)
app = SpecLLM.from_openapi("./api.yaml", config={
"timeout_seconds": 30,
"fallback_provider": backup_provider,
"cost_limit_daily": 50.0,
"cache_ttl": 3600,
})
Observability
Every response includes headers: X-SpecLLM-Request-Id, X-SpecLLM-Latency-Ms, X-SpecLLM-Tokens-Used, X-SpecLLM-Retries, X-SpecLLM-Cache-Hit.
Testing (no LLM credentials needed)
from specllm.testing.record_replay import RecordReplayProvider
# Record locally, replay in CI:
provider = RecordReplayProvider(provider=real_provider, cassette="tests/tape.json")
How It Works
flowchart TD
A[Request] --> B[Validate Input]
B -->|Invalid| C[400 Error]
B -->|Valid| D[Resolve Constraints]
D --> E{Cache Hit?}
E -->|Yes| F[Return Cached]
E -->|No| G[Build Prompt & Call LLM]
G --> H{Valid Output?}
H -->|Yes| I[Cache & Return]
H -->|No| J[Retry with Feedback]
J -->|Success| I
J -->|3x Failed| K[422 Error]
Error Handling
| Scenario | HTTP | Code |
|---|---|---|
| Bad input | 400 | INPUT_VALIDATION_FAILED |
| Output failed after retries | 422 | OUTPUT_SCHEMA_VIOLATION |
| Provider down/timeout | 503/504 | PROVIDER_UNAVAILABLE / PROVIDER_TIMEOUT |
| Cost limit hit | 503 | COST_LIMIT_REACHED |
All errors return structured JSON with code, status, message, request_id, timestamp.
License
Apache License 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file specllm-0.1.2.tar.gz.
File metadata
- Download URL: specllm-0.1.2.tar.gz
- Upload date:
- Size: 28.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
070993060b118528688e28a27a84616884a016b4db8de597d13b595b5ff3ccca
|
|
| MD5 |
79b2c97b9b32ed50b1264fa5480e7c4c
|
|
| BLAKE2b-256 |
857594ee4ab8abaa4a84f355f65182e5840a80795be55031ee4c662de46fa2c0
|
Provenance
The following attestation bundles were made for specllm-0.1.2.tar.gz:
Publisher:
publish.yml on prj1991/specllm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
specllm-0.1.2.tar.gz -
Subject digest:
070993060b118528688e28a27a84616884a016b4db8de597d13b595b5ff3ccca - Sigstore transparency entry: 2261820847
- Sigstore integration time:
-
Permalink:
prj1991/specllm@7c467639d495761c7deb8194b2b90e5e3b428977 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/prj1991
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7c467639d495761c7deb8194b2b90e5e3b428977 -
Trigger Event:
push
-
Statement type:
File details
Details for the file specllm-0.1.2-py3-none-any.whl.
File metadata
- Download URL: specllm-0.1.2-py3-none-any.whl
- Upload date:
- Size: 24.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1dec4fe30c3f581bb50d5f8eda5c150a17eb5419ec85370d1ad46886de6c04ad
|
|
| MD5 |
c5580a4c37eba20eaf5c8fe348db13b7
|
|
| BLAKE2b-256 |
e3c1e17d2d0ed79cd69e7640857a205b87a5db840bafabdc5cde998da52317e1
|
Provenance
The following attestation bundles were made for specllm-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on prj1991/specllm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
specllm-0.1.2-py3-none-any.whl -
Subject digest:
1dec4fe30c3f581bb50d5f8eda5c150a17eb5419ec85370d1ad46886de6c04ad - Sigstore transparency entry: 2261821401
- Sigstore integration time:
-
Permalink:
prj1991/specllm@7c467639d495761c7deb8194b2b90e5e3b428977 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/prj1991
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7c467639d495761c7deb8194b2b90e5e3b428977 -
Trigger Event:
push
-
Statement type: