thinwrap-llm
thinwrap-llm for Python — one Chat facade over 18 LLM providers (15 OpenAI-compatible + native Anthropic, Bedrock, Gemini) with streaming, plus embeddings on 11. Zero runtime deps (stdlib urllib), synchronous, stateless, BYO transport.
Python constraints
- Runtime dependencies
- Zero runtime dependencies
- BYO HTTP client
- Transport protocol: any object with send(HttpRequest) -> HttpResponse, passed as transport=
- Provenance
- PEP 740 attestations (PyPI Trusted Publishing)
- Minimum runtime
- Python 3.10+
- Truly zero dependencies — the standard library urllib.request is the only HTTP surface, and AWS SigV4 (SES, SNS, Bedrock) / JWT signing (FCM RS256, APNs ES256) are implemented in pure Python on hashlib and hmac, with no cryptography package.
- Synchronous by design. For async or pooled HTTP, inject an httpx or requests based Transport; the library never imports either.
- The default transport never follows redirects — a 3xx surfaces as an error rather than silently re-sending auth headers to another host.
30-second install
pip install thinwrap-llm2-minute end-to-end example
One Chat facade, every provider. Switching vendor is the provider id plus the model — the ChatInput andChatResult shapes stay identical, and every failure is aConnectorError with a typed provider_code.
import os
from thinwrap.llm import Chat, ChatInput, ChatMessage, OpenAICompatConfig, ProviderID, ConnectorError
chat = Chat(ProviderID.OPENAI, OpenAICompatConfig(api_key=os.environ["OPENAI_API_KEY"]))
try:
res = chat.complete(ChatInput(
model="gpt-5.5",
messages=[ChatMessage(role="user", content="Say hi in one word.")],
))
print(res.message.content, res.usage.total_tokens)
except ConnectorError as e:
print(e.provider_code.value, e.provider_message)The 15 OpenAI-compatible providers all takeOpenAICompatConfig; Anthropic, Bedrock, and Gemini are native adapters with their own config classes. Samecomplete() shape throughout.
Supported providers
| Provider | Operations | Auth | README |
|---|---|---|---|
openai OpenAI | chat, embeddings | bearer | docs |
azure-openai Azure OpenAI | chat, embeddings | header | docs |
anthropic Anthropic | chat | header | docs |
gemini Google Gemini | chat | header | docs |
bedrock AWS Bedrock | chat | aws-sig-v4 | docs |
mistral Mistral | chat, embeddings | bearer | docs |
cloudflare Cloudflare Workers AI | chat, embeddings | bearer | docs |
together Together AI | chat, embeddings | bearer | docs |
fireworks Fireworks AI | chat, embeddings | bearer | docs |
deepinfra DeepInfra | chat, embeddings | bearer | docs |
openrouter OpenRouter | chat, embeddings | bearer | docs |
groq Groq | chat | bearer | docs |
deepseek DeepSeek | chat | bearer | docs |
xai xAI (Grok) | chat | bearer | docs |
perplexity Perplexity | chat | bearer | docs |
vllm vLLM | chat, embeddings | bearer | docs |
ollama Ollama | chat, embeddings | bearer | docs |
lmstudio LM Studio | chat, embeddings | bearer | docs |
Migration paths
The in-process complement to a gateway
Thinwrap LLM is a thin, in-process, zero-egress facade — it does not proxy your traffic. It normalizes the request/response shape across vendors so your code stays vendor-neutral; routing, budgets, and observability stay wherever you already run them. Provider-specific request fields ride through Passthrough; the decoded vendor body is always onres.raw.
Streaming
for delta in chat.stream(ChatInput(model="gpt-5.5", messages=msgs)):
print(delta.content_delta or "", end="", flush=True)A stream cut off before the provider's terminal event raisesConnectorError rather than ending as if it were complete.
Embeddings
from thinwrap.llm import Embeddings, EmbeddingsInput
emb = Embeddings(ProviderID.OPENAI, OpenAICompatConfig(api_key=key))
out = emb.create(EmbeddingsInput(model="text-embedding-3-small", input=["hello", "world"]))
# out.embeddings is list[list[float]] in input orderEmbeddings ship on the OpenAI-float subset (11 of the 18 providers — see the table above).
Cross-language siblings
The TypeScript (/packages/llm-ts), PHP (/packages/llm-php), and Go (/packages/llm-go) variants ship the same facade shape and provider set — cross-language parity is a design goal, not a hard release gate.