llm-mesh
Async Python clients for OpenAI-compatible APIs and GigaChat, with shared request/response types, streaming, structured output, retries, and a model catalog.
Requires Python 3.11 or newer. Licensed under MIT.
Installation
Install from a checked-out repository:
python -m pip install -e /path/to/llm-mesh
Or install a reviewed Git revision:
python -m pip install "llm-mesh @ git+https://github.com/SergeyLizin/llm-mesh.git@<commit>"
The package is not yet published to PyPI.
OpenAI-compatible APIs
import asyncio
import os
from llm_mesh import LLMRequest, OpenAIClient
async def main():
client = OpenAIClient(
model=os.environ["LLM_MODEL"],
base_url=os.environ["LLM_BASE_URL"],
api_key=os.environ["LLM_API_KEY"],
)
try:
response = await client.generate_text(
LLMRequest(system="Be concise.", user="Explain HTTP caching.", max_tokens=256)
)
print(response.text)
finally:
await client.aclose()
asyncio.run(main())
GigaChatAsyncClient offers the same text, structured-output and streaming
interfaces, with GigaChat authentication. Batch helpers are available in
llm_mesh.gigachat.batch. LLMUsage normalizes provider token usage, including
cache and reasoning tokens.
Environment configuration
Set environment variables before constructing a client. The library does not
load .env files automatically. In this section, OpenAI means any endpoint
used through OpenAIClient; GigaChat means GigaChatAsyncClient.
Provider-specific environment aliases are not inferred. A catalog route may
explicitly reference any secret variable through api_key_env (and similarly
base_url_env, model_env, scope_env, or auth_url_env).
Catalog selection and connection
LLM_MODELS_CONFIG: catalog JSON path. An explicit catalog path argument takes precedence; when neither is set, the bundledmodels.jsonis used.LLM_MODEL_ID: catalog entry ID consumed bymake_selected_client(). Unset or empty means no selection and the function returnsNone.LLM_PROVIDER: route kind,openaiorgigachat, exported by catalog selection for callers that dispatch by provider. Setting it does not change the class of a directly constructed client.LLM_MODEL: provider model name exported by catalog selection. The catalog factory passes it to the client. Direct constructors use theirmodelargument; pass this variable explicitly, as in the example above.LLM_PROVIDER_LABEL: diagnostic provider label for OpenAI; defaults toopenaiunless a constructor label is supplied. Catalog selection exports the route'sproviderlabel, falling back to its kind.LLM_BASE_URL: API endpoint. Required for OpenAI unlessbase_urlis passed. GigaChat defaults tohttps://gigachat.devices.sberbank.ru/api/v1. A Batch client also inherits the endpoint of its supplied authentication client.LLM_API_KEY: OpenAI API key, or GigaChat encoded OAuth client credentials. No default. Explicit credentials take precedence. A GigaChat client may instead receive an existing access token through thetokenargument.LLM_AUTH_URL: GigaChat OAuth token endpoint; defaults tohttps://ngw.devices.sberbank.ru:9443/api/v2/oauth. Unused by OpenAI.LLM_AUTH_SCOPE: GigaChat OAuth scope, such asGIGACHAT_API_PERS,GIGACHAT_API_B2B, orGIGACHAT_API_CORP; no default scope is configured. Unused by OpenAI. These scope strings are protocol values, not environment aliases.LLM_VERIFY_SSL: TLS certificate verification. Defaults totruefor OpenAI andfalsefor GigaChat and Batch. Case-insensitive0,false, andnodisable verification; other nonempty values enable it. An explicitverifyargument takes precedence.
Options and precedence
LLM_OPTIONS is a JSON object containing request and runtime settings:
export LLM_OPTIONS='{"max_output_tokens":8192,"max_concurrent":2,"extra_body":{"top_p":0.9}}'
For every setting below, the JSON key is the lowercase environment name
without LLM_: for example, LLM_HTTP_TIMEOUT becomes http_timeout.
Use JSON booleans, numbers, and objects directly, without encoding objects as
strings. The connection and catalog selection variables above are read separately
and cannot be configured inside LLM_OPTIONS.
- Unset or empty
LLM_OPTIONSlets clients read standalone variables below. - A present JSON key overrides its standalone variable. JSON
nullselects the client default. Invalid JSON or a non-object value raisesValueError. - When
LLM_OPTIONSis present, including{}, omitted route options use client defaults and ignore old standalone values. Route options are all settings in the next three subsections (output, reasoning, and schema/usage). - Omitted runtime options in the transport/retry and Batch subsections
still fall back to standalone variables. Thus
{}does not reset those process-wide defaults.
Catalog selection manages only nine variables: the eight connection variables
from LLM_PROVIDER through LLM_VERIFY_SSL, plus LLM_OPTIONS.
route_env() builds the options object from route fields, mapping max_tokens
to max_output_tokens. It also copies http_timeout, no_degrade,
length_retries, and length_retry_cap when declared. For GigaChat,
reasoning_on.reasoning_effort supplies the default reasoning_effort option.
Other runtime controls can be set through standalone variables or a manually
constructed LLM_OPTIONS object.
apply_route_env() replaces all nine managed values, including empty values;
the CLI's --env output does the same with shell exports. Missing OAuth fields
are cleared, so declare auth_url or auth_url_env on a route that requires a
custom token endpoint. Catalog selection replaces an existing LLM_OPTIONS
object rather than merging it.
Explicit constructor overrides such as verify, http_timeout/timeout_s,
reasoning_field, and no_degrade take precedence where supported.
length_retry_cap is an exception: it supplies a default that the environment
can override. Boolean enable flags below accept case-insensitive 1, true,
and yes; other values disable them. All durations are in seconds.
Output and concurrency (route options)
LLM_MAX_OUTPUT_TOKENS: positive integer output-budget ceiling for both clients. OpenAI has no default ceiling. GigaChat uses model-family limits by default: 4096 for base models, 8192 for Pro, 16384 for Max, and 32768 for Ultra.use_model_token_limits=Falsedisables these inferred limits, but an explicit setting still applies.LLM_MIN_OUTPUT_TOKENS: positive integer floor for the requested output budget, useful for reasoning models. OpenAI only; unset means no floor.LLM_MAX_CONCURRENT: positive integer limit on concurrent outbound generation requests per client instance. Both clients; unset means no limit. GigaChat's explicitmax_concurrentargument takes precedence.LLM_FORCE_TEMPERATURE: numeric temperature override for every request. Both clients; unset preserves the request's value.LLM_FORCE_TOP_P: numerictop_poverride. GigaChat only; unset means no override. OpenAI endpoints can receivetop_pthroughextra_body.
Reasoning (route options)
LLM_DISABLE_REASONING: boolean, defaultfalse. OpenAI applies the declaredreasoning_offobject and removes declaredreasoning_onfields; without a valid off object, the configured baseline is preserved. GigaChat suppressesreasoning_effortand sendschat_template_kwargs.enable_thinking=false.LLM_REASONING_ON: JSON object declaring OpenAI request fields that enable reasoning; default{}. Merged into the extra request body.LLM_REASONING_OFF: JSON object declaring OpenAI request fields that disable reasoning; unset means no off dialect. An explicit{}removes declared on fields without adding replacement fields.LLM_DISABLE_THINKING_FOR_TOOLS: boolean, defaultfalse. OpenAI only; applies the declared off dialect to native function requests, preserving reasoning for text generation and text fallback. Requires a valid off object.LLM_REASONING_EFFORT: GigaChat default reasoning effort:low,medium, orhigh. Unset or invalid values add no effort setting. A request'sreasoning_efforttakes precedence; disabling reasoning suppresses it.LLM_REASONING_FIELD: response field containing reasoning text. OpenAI defaults to checkingreasoning_contentandreasoning; GigaChat defaults toreasoning_content. An explicitreasoning_fieldargument takes precedence.
Schema, request extensions, and usage (route options)
These settings apply to OpenAI clients only.
LLM_DISABLE_TOOLS: boolean, defaultfalse. Selects text-based emulation for structured output instead of native tool calling.LLM_TOOL_CHOICE_PREF: initial structured-output preference:auto,required, ortext. Unset uses automatic negotiation starting with strict named-function selection.autostarts with automatic function selection before trying forced forms. For multi-tool requests,requiredforces a tool call and the default isauto. An explicittool_choice_prefargument takes precedence.LLM_RESPONSE_FORMAT: declared native structured-output dialect:json_schemaorjson_object. Unset disables this additional fallback tier.json_objectguarantees JSON syntax, whilejson_schemaincludes the schema when the request provides one.LLM_OPEN_OBJECT_SCHEMAS: set tounsupportedto bypass schema-bearing tiers for schemas containing open objects. Unset leaves the client to learn this restriction from matching provider errors.LLM_SANITIZE_ENUMS: boolean, defaultfalse. Normalizes enum literals to strings for Google-compatible function schemas.LLM_EXTRA_BODY: JSON object of additional provider request fields; default{}. Existing generated body fields generally take precedence; reasoning on/off settings apply their declared overrides.LLM_EXTRA_HEADERS: JSON object of extra HTTP headers; default{}. Header values are converted to strings, and matching constructor header keys are overwritten by these settings.LLM_CACHE_HIT_FIELD: flat usage field for cache-hit tokens; defaults toprompt_cache_hit_tokens.LLM_CACHE_MISS_FIELD: flat usage field for cache-miss tokens; defaults toprompt_cache_miss_tokens.LLM_CACHE_NESTED_FIELD: dotted usage path for cache-hit tokens when flat hits are absent; defaults toprompt_tokens_details.cached_tokens. Misses are derived from prompt tokens when possible. Empty cache-field settings select the defaults; unavailable cache statistics are reported as-1.
Transport and retries (runtime options)
LLM_HTTP_TIMEOUT: HTTP timeout, default600for OpenAI and GigaChat generation,120for Batch HTTP calls. GigaChat generation uses a separate 30-second connect timeout. Explicit timeout arguments take precedence.LLM_MAX_RETRIES: additional OpenAI retries for retryable failures; default3(up to four attempts). GigaChat transient retries use constructor settings.LLM_RETRY_BACKOFF_S: OpenAI exponential-backoff base delay; default1.0. Retries add jitter and may honor a server's retry delay.LLM_LENGTH_RETRIES: nonnegative number of additional attempts after output truncation, increasing the token budget. Defaults to2for OpenAI and1for GigaChat;0disables these retries.LLM_LENGTH_RETRY_CAP: positive integer OpenAI token-budget ceiling for length retries; default32768, or the constructor'slength_retry_cap. If the minimum budget reaches an implicit cap, the cap grows to twice that minimum; an explicit environment cap is preserved. GigaChat instead uses its configured model/output ceiling.LLM_STREAM_TRANSPORT: boolean, defaultfalse. OpenAI only; uses SSE internally for non-streaming generation calls and reconstructs the final response. The normalgenerate_stream()API does not require this flag.LLM_NO_DEGRADE: boolean, defaultfalse. OpenAI only; raises instead of implicitly falling back from native structured output to text emulation. Explicit text mode and negotiation between native tool forms remain allowed.LLM_FREQUENCY_PENALTY: optional numeric OpenAIfrequency_penaltyvalue. Unset adds no value; an existingextra_bodyvalue takes precedence.LLM_REPETITION_PENALTY: optional numeric OpenAI-compatiblerepetition_penaltyvalue, with the same precedence. The endpoint must support this field; unset adds no value.
GigaChat Batch API (runtime options)
LLM_BATCH_MODE: boolean, defaultfalse. Enables Batch generation in the catalog factory for GigaChat routes. It does not change directly constructed generation clients or enable Batch for OpenAI routes.LLM_BATCH_POLL_INTERVAL_S: interval between batch status polls; default5.0.LLM_BATCH_MAX_WAIT_S: maximum wait for batch completion; default3600.0.LLM_BATCH_HTTP_RETRIES: maximum number of HTTP attempts, including the initial attempt; default12, minimum1. The budget covers rate-limit retries and an authentication refresh attempt.LLM_BATCH_429_BACKOFF_START: initial delay after HTTP 429; default1.0. Subsequent delays grow by a factor of 1.5, capped at 45 seconds.LLM_BATCH_COALESCE_MAX: maximum number of pending generation requests grouped into one batch byBatchingLLMClient; default16, minimum1.LLM_BATCH_COALESCE_DELAY_S: coalescing delay before submitting a partial group; default0.25.
Batch uses the connection/authentication settings above and LLM_HTTP_TIMEOUT.
Explicit Batch constructor settings override their environment defaults.
Request policies and streaming events
Both clients use the same transport implementation for every caller. Configure behavior explicitly instead of choosing a separate client implementation:
OpenAIClient(fallback_policy="preserve")exposes corrupt arguments, schema validation failures, open-object errors, and exhausted gateway failures without generating a replacement response. Declared open-object restrictions do not bypass the native request in this mode; the actual provider response is used. A multi-tool text turn is returned as text rather than forced into another call. Nativetool_choicenegotiation after an explicit unsupported-form error and normal transport/length retries remain enabled. Implicit text fallback is rejected; explicitly selected text emulation still works. The default policy is"recover". This constructor policy is independent ofLLM_NO_DEGRADE.OpenAIClient(validate_schema=False)returns parsed structured arguments without retrying schema violations, so callers can score the original model response. The default isTrue; this policy covers tool and response-format tiers. JSON parsing and transport error handling still apply.OpenAIClient(no_degrade=True)raises when native structured output would otherwise fall back to text emulation. Explicit text mode remains available.length_retry_capsets the default OpenAI output budget ceiling for length retries;LLM_LENGTH_RETRY_CAPoverrides it.reasoning_fieldselects the response field to read and takes precedence overLLM_REASONING_FIELD.tool_choice_pref="required"selects the initial OpenAI tool-choice form.disable_thinking_for_tools=Trueapplies the declared reasoning-off dialect only to native function requests.GigaChatAsyncClient(tool_choice="auto")sends the tools fromLLMRequest.toolsusing legacy automatic function selection. The default"single"forcesfunction_namewithschema_. Native modern tool loops (tools_required) are unsupported by the GigaChat client and raise a validation error.GigaChatAsyncClient(use_model_token_limits=False)leaves the output limit to the caller unlessLLM_MAX_OUTPUT_TOKENSsupplies an explicit ceiling.LLMRequest(mode="json_schema", schema=...)selects native JSON Schema output.
LLM_REASONING_ON and LLM_REASONING_OFF contain JSON objects describing an
OpenAI-compatible provider's request fields. Disabling reasoning uses only the declared off
object; an empty object removes declared on fields. If the off dialect is
missing or invalid, the client preserves the configured baseline and does not
invent vendor fields. Catalog entries expose these objects as reasoning_on
and reasoning_off.
Alongside generate_stream(), both clients expose generate_stream_events().
It yields typed content, reasoning, tool, completion, and error events from
llm_mesh.stream_events; Complete includes available usage and finish reason.
A transport failure yields Error and then raises the corresponding exception.
EventStreamGenerator describes this interface for type checking.
For synchronous model discovery before constructing a client, use
llm_mesh.list_models(base_url, api_key). It returns the endpoint's model IDs
in their advertised order; selecting a model belongs to the caller.
Package layout
Provider code is grouped in llm_mesh.openai and llm_mesh.gigachat.
Each contains client.py and private _common.py helpers. OpenAI-compatible
model discovery lives in openai/discovery.py; the GigaChat Batch API lives
in gigachat/batch.py. Request and response types, retries, SSE parsing,
the built-in canary, optional hooks, and the public model catalog remain
shared at the package root.
Import clients from llm_mesh, llm_mesh.openai, or llm_mesh.gigachat.
Provider subpackages also export list_models and the GigaChat batch clients,
respectively.
Model catalog
The library ships a shared public models.json with models and ordered provider
routes. A project can keep its additional models in its own models.json, without
copying or modifying the public catalog. Credentials are supplied through the
environment variables named by the routes. Loading a catalog makes no API calls.
from llm_mesh.models_catalog import load_catalog, resolve_route, make_client
routes = load_catalog()
# Choose an id@provider present in the catalog and configure its credentials:
route = resolve_route("your-model-id@your-provider", routes)
client = make_client(route)
# Use the client and await client.aclose() when finished.
To add project models, copy examples/models.json into your
project and edit its entries. extends includes all models from the public catalog:
{
"extends": "llm-mesh",
"models": [
{
"id": "custom-model",
"kind": "openai",
"providers": [{
"name": "custom",
"model_env": "CUSTOM_LLM_MODEL",
"base_url_env": "CUSTOM_LLM_BASE_URL",
"api_key_env": "CUSTOM_LLM_API_KEY"
}]
}
]
}
Load both catalogs together by specifying the project file:
routes = load_catalog("/path/to/your/project/models.json")
# Public model ids and custom-model@custom are now available in routes.
Alternatively, set LLM_MODELS_CONFIG=/path/to/your/project/models.json and call
load_catalog() without arguments. An explicit path takes precedence over this
variable. With neither, only the bundled catalog is loaded. Files in the working
directory are never picked up implicitly.
New ids are appended to the public catalog. If an id already exists, the project
entry replaces that complete model, including its provider list. Public catalog
updates remain available unless the project overrides the same id. Optional
exclude removes ids. order lists unique known ids to place first; unlisted
ids follow in catalog order, so newly added public models remain available.
Use patches to change individual fields without freezing the entire model:
{
"extends": "llm-mesh",
"patches": [
{
"id": "existing-model-id",
"providers": [{"name": "existing-provider", "max_concurrent": 2}],
"exclude_providers": ["unwanted-provider"]
}
]
}
Patches apply after models and before exclude/order. Each patch must name
an existing model id. Provider changes match by name; new names append routes,
while unmentioned routes and fields keep inheriting public updates. Model and
provider fields are shallow overrides: an object such as extra_body replaces
that whole field, and null removes a field. Use exclude_providers to remove
routes explicitly. Duplicate patch ids/names are rejected. Full replacements
through models remain available when intentional isolation is required.
Provider order within a model is preserved. For a fully independent catalog,
a plain JSON array is also supported and replaces the public catalog. Use
load_catalog_data() for nested entries, load_catalog() for flattened routes,
and catalog_digest() for an effective catalog fingerprint.
make_client() uses the legacy environment-based configuration and updates
process-wide variables. Construct catalog clients serially, before issuing
requests. For independent configuration use explicit client constructors.
Canary checks
Set a session marker with llm_mesh.canary.set_canary_context_token(). Clients
append it to the system prompt and scan responses. Reset the token in a
finally block so it cannot leak into later calls.
from llm_mesh.canary import reset_canary_context_token, set_canary_context_token
token = set_canary_context_token("session-marker")
try:
...
finally:
reset_canary_context_token(token)
A different prompt or detector can replace the defaults through
llm_mesh.hooks.configure_canary_hooks().
Development
Write all documentation, comments, catalog notes, built-in prompts, log messages, errors, and test fixtures in English. Caller-provided text and provider responses retain their original language; the library does not translate them. Keep descriptions and examples self-contained, without references to private repositories, internal issue trackers, or the library's extraction history.
python -m venv .venv
.venv/bin/python -m pip install -e '.[test]'
.venv/bin/python -m pytest -q
.venv/bin/python -m build
.venv/bin/python -m twine check dist/*
Tests use mocked HTTP transports and do not require provider credentials.
Releases
.github/workflows/release.yml runs on tags such as v1.0.0. It runs the test
matrix, checks that the tag matches project.version in pyproject.toml, builds
the wheel and source distribution, and checks an installed wheel outside the
checkout. The same distribution artifacts are then published to PyPI and
attached to a GitHub Release with generated release notes.
Before the first release, create the GitHub environment pypi and configure a
PyPI Trusted Publisher
(or a pending publisher for a new project) with these exact settings:
- Project:
llm-mesh. - GitHub owner:
SergeyLizin. - Repository:
llm-mesh. - Workflow filename:
release.yml. - Environment:
pypi.
Publishing uses GitHub OIDC; no PyPI API token secret is needed. Restrict the
pypi environment to release tags (v*). The workflow files do not create the
PyPI publisher or configure the GitHub environment automatically.
For a release, update project.version, commit the final contents, then push
the matching tag. For example, once version 1.0.0 is ready:
git tag -a v1.0.0 -m "Release 1.0.0"
git push origin main
git push origin v1.0.0
A manual Run workflow invocation only tests and builds downloadable artifacts, even when a tag is selected; it never publishes. Use it to rehearse the pipeline. Once a version is published to PyPI, release changes under a new version. If PyPI succeeds but GitHub Release creation fails, rerun only the failed job to reuse the already-built artifacts without republishing to PyPI.
Metadata
Release files for llm-mesh 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| llm_mesh-1.0.0.tar.gz | 149.9 kB | With provenance | Details for llm_mesh-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| llm_mesh-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for llm_mesh-1.0.0-py3-none-any.whl |
Total release size: 241.4 kB
Release files / llm_mesh-1.0.0.tar.gz
| Download URL | llm_mesh-1.0.0.tar.gz |
|---|---|
| Size | 149.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
460c1544f975a00428ce42a54875f9b5b59c2d0be70580ffeb590b63600194af
|
|
BLAKE2b-256 checksum How to use checksums |
157f4efd0b4e90adcbe209a5f0145caaabb5d67288744a352e94eb9359465990
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / llm_mesh-1.0.0-py3-none-any.whl
| Download URL | llm_mesh-1.0.0-py3-none-any.whl |
|---|---|
| Size | 91.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4d541892533909c36272de775fc314d4040871492d85bf05e81f2ae294c9a231
|
|
BLAKE2b-256 checksum How to use checksums |
a40f888d587d72d2bf13ab7d4211c7d73b3a5b05bba7e8a5690460833745aa90
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logSecurity for llm-mesh 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for llm_mesh-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.