plyllm 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. plyllm-1.0.0/.claude/skills/update-models/SKILL.md +52 -0
  2. plyllm-1.0.0/.claude/skills/update-models/references/sources.md +198 -0
  3. plyllm-1.0.0/.env.example +28 -0
  4. plyllm-1.0.0/.github/workflows/ci.yml +85 -0
  5. plyllm-1.0.0/.github/workflows/publish.yml +57 -0
  6. plyllm-1.0.0/.gitignore +220 -0
  7. plyllm-1.0.0/.prettierignore +6 -0
  8. plyllm-1.0.0/.prettierrc +4 -0
  9. plyllm-1.0.0/.python-version +1 -0
  10. plyllm-1.0.0/CHANGELOG.md +31 -0
  11. plyllm-1.0.0/LICENSE +21 -0
  12. plyllm-1.0.0/Makefile +62 -0
  13. plyllm-1.0.0/PKG-INFO +225 -0
  14. plyllm-1.0.0/README.md +179 -0
  15. plyllm-1.0.0/pyproject.toml +95 -0
  16. plyllm-1.0.0/scripts/release.py +122 -0
  17. plyllm-1.0.0/src/plyllm/__init__.py +66 -0
  18. plyllm-1.0.0/src/plyllm/client.py +221 -0
  19. plyllm-1.0.0/src/plyllm/concurrency.py +55 -0
  20. plyllm-1.0.0/src/plyllm/errors.py +77 -0
  21. plyllm-1.0.0/src/plyllm/llm_config.py +151 -0
  22. plyllm-1.0.0/src/plyllm/message.py +27 -0
  23. plyllm-1.0.0/src/plyllm/model.py +192 -0
  24. plyllm-1.0.0/src/plyllm/model_config.py +73 -0
  25. plyllm-1.0.0/src/plyllm/options.py +14 -0
  26. plyllm-1.0.0/src/plyllm/providers/__init__.py +0 -0
  27. plyllm-1.0.0/src/plyllm/providers/claude/__init__.py +0 -0
  28. plyllm-1.0.0/src/plyllm/providers/claude/client.py +123 -0
  29. plyllm-1.0.0/src/plyllm/providers/claude/models.py +190 -0
  30. plyllm-1.0.0/src/plyllm/providers/gemini/__init__.py +0 -0
  31. plyllm-1.0.0/src/plyllm/providers/gemini/client.py +186 -0
  32. plyllm-1.0.0/src/plyllm/providers/gemini/models.py +254 -0
  33. plyllm-1.0.0/src/plyllm/providers/grok/__init__.py +0 -0
  34. plyllm-1.0.0/src/plyllm/providers/grok/client.py +126 -0
  35. plyllm-1.0.0/src/plyllm/providers/grok/models.py +80 -0
  36. plyllm-1.0.0/src/plyllm/providers/huggingface/__init__.py +0 -0
  37. plyllm-1.0.0/src/plyllm/providers/huggingface/client.py +134 -0
  38. plyllm-1.0.0/src/plyllm/providers/huggingface/models.py +28 -0
  39. plyllm-1.0.0/src/plyllm/providers/metacentrum/__init__.py +0 -0
  40. plyllm-1.0.0/src/plyllm/providers/metacentrum/client.py +186 -0
  41. plyllm-1.0.0/src/plyllm/providers/metacentrum/models.py +103 -0
  42. plyllm-1.0.0/src/plyllm/providers/mistral/__init__.py +0 -0
  43. plyllm-1.0.0/src/plyllm/providers/mistral/client.py +144 -0
  44. plyllm-1.0.0/src/plyllm/providers/mistral/models.py +74 -0
  45. plyllm-1.0.0/src/plyllm/providers/openai/__init__.py +0 -0
  46. plyllm-1.0.0/src/plyllm/providers/openai/client.py +150 -0
  47. plyllm-1.0.0/src/plyllm/providers/openai/models.py +163 -0
  48. plyllm-1.0.0/src/plyllm/providers/providers.py +11 -0
  49. plyllm-1.0.0/src/plyllm/py.typed +0 -0
  50. plyllm-1.0.0/src/plyllm/rate_limit.py +165 -0
  51. plyllm-1.0.0/src/plyllm/response.py +84 -0
  52. plyllm-1.0.0/src/plyllm/router.py +119 -0
  53. plyllm-1.0.0/src/plyllm/tokens.py +33 -0
  54. plyllm-1.0.0/src/plyllm/utils.py +34 -0
  55. plyllm-1.0.0/tests/conftest.py +42 -0
  56. plyllm-1.0.0/tests/test_claude_provider.py +188 -0
  57. plyllm-1.0.0/tests/test_gemini_embeddings.py +154 -0
  58. plyllm-1.0.0/tests/test_huggingface_provider.py +84 -0
  59. plyllm-1.0.0/tests/test_llm_clients.py +127 -0
  60. plyllm-1.0.0/tests/test_llm_costs.py +73 -0
  61. plyllm-1.0.0/tests/test_llm_errors.py +271 -0
  62. plyllm-1.0.0/tests/test_llm_live.py +180 -0
  63. plyllm-1.0.0/tests/test_llm_options.py +85 -0
  64. plyllm-1.0.0/tests/test_llm_router.py +252 -0
  65. plyllm-1.0.0/tests/test_metacentrum_provider.py +140 -0
  66. plyllm-1.0.0/tests/test_mistral_provider.py +249 -0
  67. plyllm-1.0.0/tests/test_openai_embeddings.py +69 -0
  68. plyllm-1.0.0/tests/test_rate_limit.py +170 -0
  69. plyllm-1.0.0/tests/test_retired_models.py +70 -0
  70. plyllm-1.0.0/tests/test_served_models.py +94 -0
  71. plyllm-1.0.0/tests/test_structured_output.py +247 -0
  72. plyllm-1.0.0/uv.lock +2072 -0
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: update-models
3
+ description: Update supported model definitions using current official provider documentation.
4
+ ---
5
+
6
+ Read `references/sources.md` before step 2. For every provider it gives the exact official
7
+ documentation URLs to fetch, the repository's conventions for that provider (which pricing tier,
8
+ the cache-read multiplier, how thinking and effort are encoded), and the places where a provider's
9
+ own pages contradict each other. It is a shortcut past the search, not a substitute for the docs:
10
+ always re-fetch the pages and verify against them. Update `references/sources.md` in the same change
11
+ whenever a URL moves, a provider is added, or a convention changes.
12
+
13
+ For every model provider supported by this repository:
14
+
15
+ 1. Inspect the repository to find the provider's model definitions and related metadata.
16
+ 2. Check the provider's official developer documentation for:
17
+ - current API model IDs
18
+ - pricing in USD
19
+ - reasoning/thinking support and available settings
20
+ - deprecated, renamed, or newly available models
21
+
22
+ 3. Update the repository with the latest verified information.
23
+ - Preserve existing data structures, naming conventions, and pricing units.
24
+ - Add new supported models and update outdated entries.
25
+ - Remove or deprecate models only when appropriate for the repository.
26
+ - Do not guess undocumented pricing or capabilities.
27
+
28
+ 4. Update related aliases, tests, fixtures, or documentation when required.
29
+ 5. Run the relevant tests, formatter, and type checks. Then run `make test-models`: it is free (each
30
+ provider's model-list endpoint, keys from .env) and compares every active registry model with the
31
+ ids the provider serves right now. Read a failure before changing anything:
32
+ - `no longer serves [...]`: first check whether the id is an alias the listing omits (Anthropic
33
+ lists only the dated snapshot of some models, e.g. `claude-haiku-4-5-20251001`). If so, switch
34
+ `model_string` to the listed id the docs say it resolves to. Mark a model `retired=True` only
35
+ when the docs or the listing show it is really gone; never delete it, recorded results need it.
36
+ - An error raised while listing (401, 403, "used all available credits", a timeout) is an
37
+ account or network problem, not a registry one. Report it and leave the registry alone.
38
+ - A warning that a retired model is served again: report it; do not un-retire on your own.
39
+
40
+ `make test-models` only checks models already in the registry; it cannot see one that was left
41
+ out. `references/sources.md` lists models that were removed or skipped because the account could
42
+ not use them (e.g. grok-4.7 / grok-4.6), each with a free command to check whether they are served
43
+ now. Run those checks too, and re-add a model as soon as it is served.
44
+
45
+ 6. Summarize:
46
+ - providers checked
47
+ - models added, updated, or removed
48
+ - pricing or reasoning changes
49
+ - validation performed, including the `make test-models` result per provider
50
+ - official documentation used
51
+
52
+ Use official provider documentation as the source of truth. Research every provider supported by the repository, not only providers such as OpenAI, Gemini, or Anthropic.
@@ -0,0 +1,198 @@
1
+ # Provider documentation sources
2
+
3
+ Where the model data in `src/plyllm/providers/*/models.py` comes from. Verified 2026-10-09.
4
+
5
+ Fetch the pricing page and the per-model pages in parallel; the pricing pages carry every model's
6
+ rates in one table, and the per-model pages are the only place that lists a model's exact set of
7
+ reasoning/effort levels. A general "reasoning" guide and a per-model page sometimes disagree about
8
+ which levels a model accepts — prefer the intersection, because an unsupported level is a runtime
9
+ 400 while a missing one only blocks a config.
10
+
11
+ Batch prices (checked 2026-10-10) are a `batch_ratio` on each model, applied to input, cached input and
12
+ output: OpenAI 0.5 (every Batch row is half the Standard row; gpt-5.4 cached shows $0.13, rounded), Claude
13
+ 0.5 (stacks with the cache multipliers), Mistral 0.5 (whether it covers cached input is not stated),
14
+ Gemini 0.5 (see its section for cache reads), Grok per model (0.8 on grok-4.3 and grok-4.20; no discount
15
+ listed for the others, which get `None`), Metacentrum `None` (free). Batch: OpenAI
16
+ <https://developers.openai.com/api/docs/guides/batch>, Anthropic pricing page, Gemini
17
+ <https://ai.google.dev/gemini-api/docs/batch-mode>, xAI <https://docs.x.ai/developers/pricing>, Mistral
18
+ <https://mistral.ai/pricing>.
19
+
20
+ ## Anthropic (`claude`)
21
+
22
+ - Pricing (input / output / cache read + write, batch, all models): <https://platform.claude.com/docs/en/about-claude/pricing>
23
+ - Model lineup, IDs, context, max output, thinking mode, default effort: <https://platform.claude.com/docs/en/about-claude/models/overview>
24
+ - Effort levels per model, and per-model recommendations: <https://platform.claude.com/docs/en/build-with-claude/effort>
25
+ - One model's full spec: `https://platform.claude.com/docs/en/models/<slug>/overview` (e.g. `opus-5-5`, `fable-5-1`)
26
+ - Deprecations / retirements: <https://platform.claude.com/docs/en/about-claude/model-deprecations>
27
+
28
+ `docs.claude.com/en/docs/...` 302-redirects to `platform.claude.com/docs/en/...`; fetch the
29
+ `platform.claude.com` URL directly to save a round trip.
30
+
31
+ Repository notes: `cache_write_cost` is the 5-minute cache-write rate, 1.25x input on every model
32
+ (`_CACHE_WRITE_RATIO`); the client caches the system prompt with the default 5-minute lifetime, so the
33
+ 1-hour rate (2x) is not stored. `cached_input_cost` is the cache-read rate, normally 0.1x input but 0.025x on
34
+ Fable 5.1 and 0.05x on Opus 5.5 and Sonnet 5.5. `cache_write_cost` is the 5-minute cache-write rate,
35
+ 1.25x input on every model (`_CACHE_WRITE_RATIO`); the client caches the system prompt with the default
36
+ 5-minute lifetime, so the 1-hour rate (2x) is never charged. The pricing page says the batch discount
37
+ stacks with both cache multipliers. The models endpoint lists some older models only by dated snapshot (`claude-haiku-4-5-20251001`,
38
+ `claude-opus-4-5-20251101`, `claude-sonnet-4-5-20250929`), not by alias, so use the snapshot there. Haiku 5.5 has two price tiers (prompts up to / over
39
+ 100k tokens); the repository stores the lower one. "Adaptive (always on)" in the docs maps to `thinking_only=True`.
40
+ Models whose docs say thinking cannot be disabled at any effort are `thinking_only`; Opus 5 can only
41
+ disable it at `high` or below, which `_THINKING_ONLY_EFFORTS` already encodes. Sonnet 5.5 rejects
42
+ `"disabled"` and only offers `thinking: {"type": "between_tools"}` (effort `high` or below), which the
43
+ harness does not model, so it is `thinking_only`. The per-model thinking table ("Rejected with 400")
44
+ is at <https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting>. Its footnote
45
+ names only Opus 5 and Haiku 5.5 as rejecting `"disabled"` at `xhigh`/`max`; the repository applies
46
+ that rule to every Claude model (`_THINKING_ONLY_EFFORTS`), which the docs neither confirm nor rule
47
+ out for Opus 4.8, Sonnet 5 and Sonnet 4.6.
48
+
49
+ ## OpenAI (`openai`)
50
+
51
+ - Pricing, all models, input / cached input / output: <https://developers.openai.com/api/docs/pricing>
52
+ - Current model list: <https://developers.openai.com/api/docs/models>
53
+ - Per-model effort values, default and pricing: `https://developers.openai.com/api/docs/models/<model-id>`
54
+ - Reasoning guide (general, does not enumerate per model): <https://developers.openai.com/api/docs/guides/reasoning>
55
+
56
+ `platform.openai.com/docs/...` 301-redirects to `developers.openai.com/api/docs/...`.
57
+
58
+ Repository notes: the per-model pages list `none` as an effort value; the harness expresses that as
59
+ `thinking_enabled=False` (the provider sends effort `none`), so `none` is never in a `supported_reasoning`
60
+ set. A model whose page does not list `none` is `thinking_only` (on 2026-10-09: gpt-6-astra, gpt-6.1-sol,
61
+ the Pro models, and gpt-5-mini / gpt-5-nano, whose pages list no efforts at all). A model whose default
62
+ effort is `none` (gpt-5.4, -mini, -nano) is `needs_effort_to_think`, or thinking on would not reason.
63
+ Temperature is accepted only at effort `none` (GPT-6 migration guide, "When reasoning effort is not none,
64
+ remove temperature, top_p"), which `OpenAILLMModel` derives from `thinking_only`. Effort sets differ
65
+ within a generation — GPT-6.x and GPT-5.6 accept `max`, GPT-5.5 and GPT-5.4 stop at `xhigh`, and the
66
+ Pro variants start at `medium`. Cached input is not always 0.1x: gpt-6.1-sol is 0.05x. gpt-6.1-sol rejects `none` (thinking cannot be
67
+ turned off), and the gpt-6-astra page does not list `none`. gpt-5.6-sol is on promotional pricing "at
68
+ least through November 21, 2026". The gpt-5-mini / gpt-5-nano pages no longer list effort values.
69
+ Shutdown dates: <https://developers.openai.com/api/docs/deprecations>.
70
+
71
+ ## Google Gemini (`gemini`)
72
+
73
+ - Pricing, paid tier, per model: <https://ai.google.dev/gemini-api/docs/pricing>
74
+
75
+ `cached_input_cost` is the "Context caching price" row (hourly storage is not modelled). The batch table
76
+ lists its own cache-read price; where it is not half of the standard one (2.5 models, 3.1 Pro, 3 Flash:
77
+ same as standard; 3.5 Flash-Lite: 0.02), record it as `batch_cached_input_cost`. The batch-mode page says
78
+ batch cache hits pay "the standard context caching rates", which contradicts the table for 3.8 / 3.6 Flash
79
+ and 3.1 Flash-Lite; the table is used. 3.7 Flash and 3.5 Flash had no pricing block on 2026-10-10, so
80
+ their cache price is 0 until the page lists one.
81
+
82
+ - Model list, stable vs preview: <https://ai.google.dev/gemini-api/docs/models>
83
+ - Deprecations and shutdown dates: <https://ai.google.dev/gemini-api/docs/deprecations>
84
+ - Embedding task prefixes (`task:` / `title:`), verbatim: <https://ai.google.dev/gemini-api/docs/embeddings>
85
+
86
+ Repository notes: Flash pricing is promotional and dated — 3.8 / 3.7 / 3.6 Flash are $0.75 / $3.75
87
+ through 2026-12-31 and $1.50 / $7.50 after. Record the rate in force, with a comment giving the
88
+ change date. Pro models have a >200k-token tier; the repository stores the standard tier only.
89
+ The models page lists preview models that the deprecations page has already shut down — always
90
+ cross-check both before keeping a `-preview` id (on 2026-10-09 it still listed
91
+ `gemini-embedding-2-preview`, which shut down on 2026-08-10). The thinking guide
92
+ (<https://ai.google.dev/gemini-api/docs/thinking>) is the per-model table of thinking levels and
93
+ defaults; it no longer documents `thinking_budget`, which the 2.5 models still use here. 3.8 and 3.7
94
+ Flash reject `minimal`. 3.1 Flash-Lite is not in that table.
95
+
96
+ ## xAI Grok (`grok`)
97
+
98
+ - Model list and pricing table: <https://docs.x.ai/docs/models>
99
+ - Per-model page, with the effort list and default: `https://docs.x.ai/docs/models/<model-id>`
100
+ - Reasoning guide: <https://docs.x.ai/docs/guides/reasoning>
101
+
102
+ Repository notes: prices are the standard sub-200k-token tier; requests reaching 200k are billed at
103
+ roughly double for the whole request. The reasoning guide and the per-model pages disagree about
104
+ `xhigh` on grok-4.5 (the guide says it is silently treated as `high`) and about whether grok-4.3
105
+ reasons at all — both are kept at low/medium/high. The guide documents `reasoning_effort` only for
106
+ grok-4.7 / 4.6 / 4.5, and the grok-4.20 page has no effort section, so grok-4.20 takes none.
107
+ Reasoning cannot be disabled on grok-4.7 / 4.6 / 4.5, so they are `thinking_only`, as is grok-4.20
108
+ (no effort section; it always reasons); grok-4.3 accepts `none` (default `low`), which the provider sends
109
+ when thinking is off. No page documents `temperature` for the reasoning models, so every Grok model has
110
+ `TemperatureSupport.NEVER`; nor is `reasoning.summary` documented (it is still sent with an effort). Retirements: <https://docs.x.ai/developers/migration/may-15-retirement>.
111
+
112
+ **Waiting to be re-added: grok-4.7 and grok-4.6.** Removed on 2026-10-09: the docs list them, but the
113
+ API returns 404 for this account ("does not exist or your team does not have access to it"). On every
114
+ update, check whether they are served now (free, needs `GROK_API_KEY`):
115
+
116
+ ```
117
+ uv run python -c "import asyncio; from plyllm import LLM, LLMConfig, Provider; print(sorted(asyncio.run(LLM(LLMConfig.from_env()).served_models(Provider.GROK))))"
118
+ ```
119
+
120
+ If the list contains them, add them back with the values from their model pages (on 2026-10-09:
121
+ $2.00 / $6.00, cached $0.50; effort low/medium/high/xhigh, default high; reasoning cannot be
122
+ disabled), run `make test-models`, and delete this paragraph.
123
+
124
+ ## Mistral (`mistral`)
125
+
126
+ - API pricing per model: <https://docs.mistral.ai/inference/pricing> (`mistral.ai/pricing/api` 301-redirects here)
127
+ - One model's page, with its API id: `https://docs.mistral.ai/models/<slug>` (e.g. `mistral-large-4-0`,
128
+ `mistral-medium-3-5-26-04`, `mistral-small-4-0-26-03`; the undated slugs of the last two 404)
129
+ - Model list, dated ids, deprecations: <https://docs.mistral.ai/getting-started/models/models_overview/>
130
+ - Reasoning capability: <https://docs.mistral.ai/capabilities/reasoning/>
131
+
132
+ Repository notes: docs slugs are not API ids — the model page shows the id next to the parameter
133
+ count (`mistral-large-2512 +1` for Large 3, `mistral-large-4 +1` for Large 4). The reasoning page
134
+ lists which models take `reasoning_effort` (Small, Medium 3.5, Large 4, hosted GLM 5.3), but only
135
+ documents `high` and `none`, so `_MISTRAL_REASONING` is `{high}` (`none` is what the provider sends
136
+ when thinking is off). Do not take the set from the SDK enum (`none, minimal, low, medium, high,
137
+ xhigh`): on 2026-10-09 `mistral-small-2603` rejected `low` with "Must be one of (none, high)".
138
+ Widen the set for a model only after `make test-live` (or a single paid call, approved first) shows
139
+ the API accepts the level. Cached input is documented only as "up to -90% on input tokens", so `cached_input_cost` is
140
+ 0.1x input. Large 4 is on an undated sale; the repository records the sale price
141
+ with the list price in a comment. Large 3 and Ministral 3 are deliberately not in the repository
142
+ because their reasoning support is undocumented. Hosted GLM 5.3 (`zai-glm-5-3`) documents only
143
+ `low`/`high`/`max` and rejects `none`, which the provider sends when thinking is off; it is left out
144
+ until the provider can express that. The Medium 3.5 page lists `mistral-medium-3-5`,
145
+ `mistral-medium-3` and `mistral-medium-latest`, not the `mistral-medium-2604` the repository sends;
146
+ `make test-models` shows whether the endpoint still serves it. WebFetch has swapped price labels on
147
+ the Large 4 page; read the pricing table from raw HTML (`curl`) when in doubt.
148
+
149
+ ## Metacentrum / e-INFRA (`metacentrum`)
150
+
151
+ - Served models, API ids, aliases, obsolete models: <https://docs.cerit.io/en/docs/ai-as-a-service/chat-ai>
152
+ - API base URL: `https://llm.ai.e-infra.cz/v1/` (set locally through `METACENTRUM_BASE_URL` / `METACENTRUM_API_KEY`)
153
+
154
+ Repository notes: self-hosted and free, so every model carries `input_cost=0` / `output_cost=0`.
155
+ The service replaces models without notice and the page has a separate "Obsolete Models" table —
156
+ check it, not just the main table. The page can lag the live endpoint (on 2026-10-07 it still read
157
+ "Effective June 30, 2026" and listed `deepseek-v4-flash`, while `/v1/models` served
158
+ `deepseek-v4.1-flash` from 2026-09-28). Prefer `GET https://llm.ai.e-infra.cz/v1/models` (free,
159
+ needs `METACENTRUM_API_KEY`) and ask before calling it. Models it drops are **kept** in `models.py`
160
+ and marked `retired=True`, so existing experiment configs and recorded results still resolve and
161
+ cost (`resolve_model` raises on an unknown name) while new calls are refused. `make test-models`
162
+ (free, needs the keys in .env) compares every provider's active registry models with its served list
163
+ and fails on a mismatch. Reasoning-effort support is not documented per model, so leave
164
+ `supported_reasoning=None` unless a model is known to accept it; the only documented toggle is
165
+ DeepSeek's `chat_template_kwargs: {"thinking": true}` (reasoning is off by default), on
166
+ <https://docs.cerit.io/en/docs/ai-as-a-service/ai-api>. That page is out of date: on 2026-10-09
167
+ deepseek-v4.1-flash reasoned with and without it, and with `thinking: false`. The thinking flags come
168
+ from a free probe instead (one short prompt per model, thinking on and off, reasoning tokens counted; put
169
+ a unique id in each prompt, because the proxy caches identical prompts and ignores `extra_body` when it
170
+ does). Results on 2026-10-09: the Qwen templates read `enable_thinking` (not `thinking`), kimi-k3 reads
171
+ `thinking`, gpt-oss-120b and deepseek-v4.1-flash always reason (`thinking_only`), gemma-4 reasons only with
172
+ an effort (`needs_effort_to_think`), mistral-medium-3.5 gave no reasoning with thinking on
173
+ (`thinking=False`), and glm-5.3 answered every call with 400 "`tools` must not be an empty array". On
174
+ 2026-10-10 that happened even for a minimal `/v1/responses` request, while `/v1/chat/completions` answered
175
+ normally: the proxy's Responses-to-chat conversion breaks for this model, not the harness's request. The stable aliases (`qwen3.5`, `kimi`,
176
+ `glm`, ...) resolve to whatever is current, so they keep configs working but not outputs: `qwen3.5`
177
+ points at `qwen3.5-int4` (397B, AWQ int4).
178
+
179
+ ## Hugging Face (`huggingface`)
180
+
181
+ Local encoder checkpoints run through `transformers`; they are free, have no API pricing and no
182
+ reasoning settings. Only verify the repo id still exists, e.g.
183
+ <https://huggingface.co/princeton-nlp/sup-simcse-bert-base-uncased>.
184
+
185
+ ## After editing
186
+
187
+ ```
188
+ make format
189
+ make test
190
+ make typecheck
191
+ make test-models # free: are all active models still served? Needs the keys in .env
192
+ ```
193
+
194
+ Then check the prices and effort sets round-trip:
195
+
196
+ ```
197
+ uv run python -c "from plyllm.providers.claude.models import claude_models; print([(m.model_name, m.input_cost, m.output_cost) for m in claude_models()])"
198
+ ```
@@ -0,0 +1,28 @@
1
+ # openai configuration
2
+ OPENAI_ORGANIZATION=
3
+ OPENAI_PROJECT=
4
+ OPENAI_API_KEY=
5
+
6
+ # gemini configuration
7
+ GEMINI_PROJECT=
8
+ GEMINI_API_KEY=
9
+
10
+ # grok configuration
11
+ GROK_API_KEY=
12
+ GROK_BASE_URL=
13
+
14
+ # claude configuration
15
+ CLAUDE_API_KEY=
16
+
17
+ # mistral configuration
18
+ MISTRAL_API_KEY=
19
+
20
+ # metacentrum configuration
21
+ METACENTRUM_BASE_URL=
22
+ METACENTRUM_API_KEY=
23
+
24
+ # huggingface configuration (only needed for gated/private models)
25
+ HF_TOKEN=
26
+
27
+ # requests that share this key and a prompt prefix reuse the provider cache (OpenAI, Grok, Mistral); default PlyLLM
28
+ PROMPT_CACHE_KEY=
@@ -0,0 +1,85 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ci-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+ with:
21
+ enable-cache: true
22
+
23
+ - name: Sync dependencies
24
+ run: uv sync --group dev --python 3.13
25
+
26
+ - name: Ruff lint
27
+ run: uv run ruff check .
28
+
29
+ - name: Ruff format check
30
+ run: uv run ruff format --check .
31
+
32
+ - name: Prettier format check
33
+ run: npx --yes prettier@3.9.6 --check .
34
+
35
+ typecheck:
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+
40
+ - name: Install uv
41
+ uses: astral-sh/setup-uv@v5
42
+ with:
43
+ enable-cache: true
44
+
45
+ - name: Sync dependencies
46
+ run: uv sync --group dev --python 3.13
47
+
48
+ - name: Pyright
49
+ run: make typecheck
50
+
51
+ test:
52
+ runs-on: ubuntu-latest
53
+ strategy:
54
+ fail-fast: false
55
+ matrix:
56
+ python-version: ["3.12", "3.13"]
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+
60
+ - name: Install uv
61
+ uses: astral-sh/setup-uv@v5
62
+ with:
63
+ enable-cache: true
64
+
65
+ - name: Sync dependencies
66
+ run: uv sync --group dev --python ${{ matrix.python-version }}
67
+
68
+ - name: Run tests
69
+ run: uv run --group dev --python ${{ matrix.python-version }} pytest -q
70
+
71
+ build:
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+
76
+ - name: Install uv
77
+ uses: astral-sh/setup-uv@v5
78
+ with:
79
+ enable-cache: true
80
+
81
+ - name: Build wheel and sdist
82
+ run: uv build
83
+
84
+ - name: Check package metadata
85
+ run: uvx twine check dist/*
@@ -0,0 +1,57 @@
1
+ name: Publish
2
+
3
+ # Publishes to PyPI when a GitHub Release is published (see scripts/release.py).
4
+ # Uses PyPI Trusted Publishing (OIDC) — no API token is stored.
5
+ # One-time setup on PyPI: add a trusted publisher for project "plyllm" with
6
+ # owner=radajakub, repository=plyllm, workflow=publish.yml, environment=pypi.
7
+ on:
8
+ release:
9
+ types: [published]
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v5
19
+
20
+ - name: Verify tag matches the package version
21
+ run: |
22
+ version="$(uv version --short)"
23
+ tag="${GITHUB_REF_NAME#v}"
24
+ if [ "$version" != "$tag" ]; then
25
+ echo "tag $GITHUB_REF_NAME does not match pyproject version $version" >&2
26
+ exit 1
27
+ fi
28
+
29
+ - name: Build wheel and sdist
30
+ run: uv build
31
+
32
+ - name: Check package metadata
33
+ run: uvx twine check dist/*
34
+
35
+ - name: Upload build artifacts
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish:
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment:
45
+ name: pypi
46
+ url: https://pypi.org/p/plyllm
47
+ permissions:
48
+ id-token: write # required for trusted publishing
49
+ steps:
50
+ - name: Download build artifacts
51
+ uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+
56
+ - name: Publish to PyPI
57
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,220 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ *.lcov
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ # Pipfile.lock
97
+
98
+ # uv
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ # uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ # poetry.lock
110
+ # poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ # pdm.lock
117
+ # pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ # pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi/*
127
+ !.pixi/config.toml
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule*
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .venv
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs/Zensical documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ # PyCharm
187
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
188
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
189
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
190
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
191
+ # .idea/
192
+
193
+ # Abstra
194
+ # Abstra is an AI-powered process automation framework.
195
+ # Ignore directories containing user credentials, local state, and settings.
196
+ # Learn more at https://abstra.io/docs
197
+ .abstra/
198
+
199
+ # Visual Studio Code
200
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
201
+ # can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
202
+ # and can be added to the global gitignore or merged into this file. However, if you prefer, you
203
+ # could uncomment the following to ignore the entire vscode folder
204
+ # .vscode/
205
+ # Temporary file for partial code execution
206
+ tempCodeRunnerFile.py
207
+
208
+ # Ruff stuff:
209
+ .ruff_cache/
210
+
211
+ # PyPI configuration file
212
+ .pypirc
213
+
214
+ # Marimo
215
+ marimo/_static/
216
+ marimo/_lsp/
217
+ __marimo__/
218
+
219
+ # Streamlit
220
+ .streamlit/secrets.toml
@@ -0,0 +1,6 @@
1
+ .venv
2
+ dist
3
+ build
4
+ node_modules
5
+ *.lock
6
+ uv.lock
@@ -0,0 +1,4 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "printWidth": 180
4
+ }
@@ -0,0 +1 @@
1
+ 3.13