Small, clean Python SDK for OpenAI-compatible LLM APIs.
Project description
📦 llm-sdk
Small Python SDK for OpenAI-compatible LLM APIs.
One file, clean API, boring on purpose. Use it with local servers, OpenAI-style endpoints, structured output, tool calls, vision inputs, and reasoning streams.
✨ Features
- Sync and async clients
- Streaming and non-streaming responses
- Configurable retry with per-call override
- OpenAI Chat Completions support
- Optional OpenAI Responses API mode
- Structured output from JSON schema or typed Python classes
- Tool schema generation from Python callables
- Vision input normalization from URL, path, base64, or PIL image
- Thinking/reasoning token parsing
- Lightweight verbose stats for streams
🚀 Get Started
Install the package directly from PyPI:
pip install llm-sdk-py
If you also need PIL image support:
pip install "llm-sdk-py[pillow]"
Alternatively, since it's designed to be simple, you can still just drop llm_sdk.py directly into your project!
from llm_sdk import LLM
llm = LLM(
model="qwen3.6-27b",
base_url="http://localhost:1234",
api_key="lm-studio",
)
response = llm.response(input="Write a tiny haiku about fast code.",system="You're an helpful assistant!")
print(response["answer"])
By default, base_url="http://localhost:1234/v1" and api_key="lm-studio", so local LM Studio-style servers work with very little setup.
All inference methods accept either input="..." for the common single-user-message case or a Chat Completions-style message list + system for a system prompt:
response = llm.response(messages=[
{"role": "user", "content": "Write a tiny haiku about fast code."},
])
📡 Streaming
for event in llm.stream_response(input="Explain adapters in one paragraph."):
if event["type"] == "answer":
print(event["content"], end="", flush=True)
Events are small dictionaries:
{"type": "answer", "content": "..."}
{"type": "reasoning", "content": "..."}
{"type": "tool_call", "content": {"id": "...", "name": "...", "arguments": {...}, "callable": Callable}}
{"type": "tool_call_part", "content": {"id": "...", "name": "...", "args_delta": {...}}}
{"type": "verbose", "content": {"tokens": 42, "tokens_per_second": 91.3, "latency": 0.2, "prompt_tokens": 10, "completion_tokens": 32, "total_tokens": 42}}
{"type": "final", "content": {"answer": "..."}}
{"type": "done"}
Use final=True if you also want a final aggregated response event.
⏱️ Async
import asyncio
from llm_sdk import LLM
async def main():
async with LLM(model="gpt-5.5", api_key="sk-...", base_url="https://api.openai.com/v1", use_responses_api=True) as llm:
response = await llm.async_response(input="Give me a crisp project name.")
print(response["answer"])
asyncio.run(main())
🔄 Retry
Set max_retries globally or override it per call.
# global
llm = LLM(model="qwen3.6-27b", max_retries=5)
# per call
llm.response(input="...", max_retries=0)
📐 Structured Output
Pass a JSON schema or a typed class. Classes are converted into OpenAI-compatible JSON schema.
class Verdict:
sentiment: str
score: float
tags: list[str]
result = llm.response(
input="Review: fast, small, surprisingly nice.",
output_format=Verdict,
)
print(result["answer"])
🛠️ Tools
Pass Python callables or already-built OpenAI tool definitions. The SDK exposes tool definitions and returns streamed/final tool calls.
It does not execute tools for you. You stay in control.
def search_docs(query: str, limit: int = 5) -> str:
"""Search internal docs."""
return "..."
response = llm.response(
input="Find the auth setup notes.",
tools=[search_docs],
)
print(response.get("tool_calls", []))
For multi-turn conversation loops with tool calls, use the built-in helper functions to easily format messages:
from llm_sdk import assistant_message, tool_result, user_message
# 1. Format the assistant's response (includes both answer text and tool calls)
msg1 = assistant_message(response)
# 2. Format a tool call execution result
msg2 = tool_result(response["tool_calls"][0], "result string or dict")
# 3. Format subsequent user messages
msg3 = user_message("Tell me more about the results.")
👁️ Vision
Image content can be a URL, a local path, base64, or a PIL image.
response = llm.response([
{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{"type": "image_path", "image_path": "photo.png"},
],
}
])
Supported image forms include:
{"type": "image_url", "image_url": "https://..."}{"type": "image_path", "image_path": "local-file.png"}{"type": "image_base64", "image_base64": "..."}{"type": "image_pil", "image_pil": image}
🔌 Responses API
Use use_responses_api=True for endpoints that prefer OpenAI's Responses API shape.
llm = LLM(
model="gpt-5.5",
api_key="sk-...",
base_url="https://api.openai.com/v1",
use_responses_api=True,
)
🧠 Reasoning Effort
Use reasoning_effort="high" to set models's reasoning effort.
response = llm.response(
input="...",
reasoning_effort="high"
)
⚙️ API
response(...)andstream_response(...)async_response(...)andasync_stream_response(...)input="..."ormessages=[...]for all inference methodslist_models(...)andasync_list_models(...)max_retries=3globally onLLM(...)or per callreasoning_effort="low|medium|high"where supportedhide_thinking=Falseto stream/return reasoning contentCustomThinkingToken(...)for custom<think>-style parsingverbose=Truefor token-ish stream statswith LLM(...) as llm:/async with LLM(...) as llm:for cleanup
💡 Why
Most LLM wrappers either become frameworks or stay too close to raw HTTP. This sits in the middle: enough structure to be pleasant, little enough surface area to understand in one sitting.
📜 License
MIT
Project details
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 llm_sdk_py-1.4.0.tar.gz.
File metadata
- Download URL: llm_sdk_py-1.4.0.tar.gz
- Upload date:
- Size: 23.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eab4fbd3dd7e5a40a863075fedece186fa07c63047d7e1fa689c1ad5235b28be
|
|
| MD5 |
2bbc007888d645ed305af34e3748a1a6
|
|
| BLAKE2b-256 |
c1830015264410ae33b0b8905c38dca4204fc7cfd9789a5861246d8df81b742f
|
Provenance
The following attestation bundles were made for llm_sdk_py-1.4.0.tar.gz:
Publisher:
publish.yml on flgaertig/llm-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_sdk_py-1.4.0.tar.gz -
Subject digest:
eab4fbd3dd7e5a40a863075fedece186fa07c63047d7e1fa689c1ad5235b28be - Sigstore transparency entry: 1809056667
- Sigstore integration time:
-
Permalink:
flgaertig/llm-sdk@428ac422b83e6535038eb437f100f80597407b00 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/flgaertig
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@428ac422b83e6535038eb437f100f80597407b00 -
Trigger Event:
release
-
Statement type:
File details
Details for the file llm_sdk_py-1.4.0-py3-none-any.whl.
File metadata
- Download URL: llm_sdk_py-1.4.0-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9b0c0dcd432ee61d6d6c1987774ecdd2b5b5317c04ece11a445796006733507
|
|
| MD5 |
4f629cc4fc5194e79f992f2a578972d1
|
|
| BLAKE2b-256 |
ac94b1ddc3a7b80e5ff102e2777ac9fe25d8aaef936476ea7e2438cb195362e6
|
Provenance
The following attestation bundles were made for llm_sdk_py-1.4.0-py3-none-any.whl:
Publisher:
publish.yml on flgaertig/llm-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
llm_sdk_py-1.4.0-py3-none-any.whl -
Subject digest:
a9b0c0dcd432ee61d6d6c1987774ecdd2b5b5317c04ece11a445796006733507 - Sigstore transparency entry: 1809056703
- Sigstore integration time:
-
Permalink:
flgaertig/llm-sdk@428ac422b83e6535038eb437f100f80597407b00 -
Branch / Tag:
refs/tags/v1.4.0 - Owner: https://github.com/flgaertig
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@428ac422b83e6535038eb437f100f80597407b00 -
Trigger Event:
release
-
Statement type: