Skip to main content

A tiny, unified client for four LLM providers: claude, openai, gemini, ollama.

Project description

thinchat

PyPI Python License: MIT

English | 한국어

A tiny, unified client for four LLM providers — claude, openai, gemini, ollama.

Name a provider, then call it. Every client offers completion — whole, streamed, or JSON-structured — and, where the provider has one, embeddings, each with an async twin. No gateway, no router, no cost tracking: just the calls, over the providers' own SDKs.

How it works

flowchart LR
  M["make_client(provider)"] --> C["Client<br/>openai-compatible · or claude"]
  C --> V["complete · stream · parse · embed<br/>(+ a-prefixed async twins)"]
  V --> S{{"vendor SDK"}}
  S -->|ok| O(["str · dict · list float · stream"])
  S -->|"SDK / transport error"| E(["LLMError"])

make_client looks the provider up in one factory map: openai/gemini/ollama share a single class over the openai SDK (they differ only in data); claude has its own over anthropic. A call builds the request, hits the SDK, and either extracts the reply or maps the failure to one LLMError.

Install

pip install thinchat

Both provider SDKs (openai and anthropic) come with it, so every provider works out of the box; each is imported lazily the first time you construct its client.

Use

from thinchat import make_client

llm = make_client("claude")                     # key from CLAUDE_API_KEY
print(llm.complete("Say hi in one word."))
print(llm.complete("Name a color.", system="Answer in one word."))   # system= steers any verb

# Structured output: reply parsed into a JSON object. The schema steers generation
# but is not validated locally, so check the returned dict's fields yourself.
verdict = llm.parse(
    "Is this an ad? 'Buy now, 50% off — order today'",
    schema={"type": "object",
            "properties": {"is_ad": {"type": "boolean"}, "reason": {"type": "string"}},
            "required": ["is_ad"]},
)
print(verdict["is_ad"])

# Streaming.
for chunk in make_client("claude").stream("Count to five."):
    print(chunk, end="")

# Embeddings (openai / gemini / ollama; Claude has none).
vectors = make_client("openai").embed(["hello", "world"])

Every verb has an async twin — acomplete, astream, aparse, aembed:

llm = make_client("claude")
text = await llm.acomplete("Summarize in one line: ...")

Providers

provider key env embeddings
claude CLAUDE_API_KEY no
openai OPENAI_API_KEY yes
gemini GEMINI_API_KEY yes
ollama none (local) yes

openai, gemini, and ollama speak the same OpenAI-compatible API, so one SDK serves all three; only the base URL, key, and default models differ. Ollama runs locally (OLLAMA_HOST, default http://localhost:11434) and needs no key.

Each provider takes the settings it exposes under the same name, sent only when you set them: max_tokens (reply length), temperature / top_p (sampling), and timeout in seconds / max_retries (the HTTP client) — e.g. make_client("claude", temperature=0.2, timeout=30). An unset value leaves the provider's own default in place, except max_tokens, which Anthropic requires and so defaults to 4096 for claude (the OpenAI-compatible providers omit it, letting the model decide).

Keys are read from the environment. Set them once in your shell profile (~/.bashrc, ~/.zshrc) so every session picks them up — thinchat is a library and never imposes a file location of its own:

export CLAUDE_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."
export GEMINI_API_KEY="..."          # ollama runs locally and needs no key

Or pass a key explicitly, which overrides the environment:

llm = make_client("claude", api_key="sk-ant-...", model="claude-haiku-4-5-20251001")

Capabilities

A client whose provider lacks a capability raises UnsupportedError. The capabilities are completion, streaming, structured_output, and embeddings; check first with supports:

make_client("claude").supports("embeddings")   # False

Errors

Everything thinchat raises on purpose derives from ThinchatError, so one except handles the package's failures:

  • UnknownProviderError — the name isn't one of the four providers.
  • ProviderUnavailableError — the provider's SDK isn't installed, or no API key is set.
  • UnsupportedError — the provider lacks the capability (e.g. embeddings on Claude).
  • LLMError — the API call failed, or the reply was empty or malformed.

Lifecycle

A client holds an HTTP connection pool. For a one-off script you can ignore it; for a server that builds a client per request, close it so connections do not leak — use it as a context manager, or call close() / aclose():

with make_client("claude") as llm:
    llm.complete("...")                 # sync: closes the pool on exit

async with make_client("claude") as llm:
    await llm.acomplete("...")          # async: closes the async pool too

close() frees the sync pool; if you drove async verbs, release with aclose() or async with so the async pool is closed as well.

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

thinchat-0.1.1.tar.gz (28.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

thinchat-0.1.1-py3-none-any.whl (21.3 kB view details)

Uploaded Python 3

File details

Details for the file thinchat-0.1.1.tar.gz.

File metadata

  • Download URL: thinchat-0.1.1.tar.gz
  • Upload date:
  • Size: 28.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for thinchat-0.1.1.tar.gz
Algorithm Hash digest
SHA256 266bf247f0eb721f9eeba578224034680227a4789b3cb979d8ea2933ae9559ba
MD5 ea9a422584c5776960b2eda5f18108db
BLAKE2b-256 4f2e8d230c740594b695ef634bf89426b5ab578b5b61530f553006ef8cca6df4

See more details on using hashes here.

File details

Details for the file thinchat-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: thinchat-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 21.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for thinchat-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6151965e6cf097e9dbd29231574d81b516b52443bc0117045db0d1a8b85028d8
MD5 4244d839153f27b8c3f9968f2f9e0d29
BLAKE2b-256 cd6716c4aba75ffb21aab8de2a485205b6d66d96d6897e2ec33ab79e5570a441

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page