dsh-openai-shim 0.2.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.
- dsh_openai_shim-0.2.0/.dockerignore +8 -0
- dsh_openai_shim-0.2.0/.gitignore +8 -0
- dsh_openai_shim-0.2.0/CHANGELOG.md +66 -0
- dsh_openai_shim-0.2.0/LICENSE +9 -0
- dsh_openai_shim-0.2.0/PKG-INFO +228 -0
- dsh_openai_shim-0.2.0/README.md +204 -0
- dsh_openai_shim-0.2.0/dsh_openai_shim/__init__.py +432 -0
- dsh_openai_shim-0.2.0/dsh_openai_shim/cli.py +98 -0
- dsh_openai_shim-0.2.0/dsh_openai_shim/config.py +355 -0
- dsh_openai_shim-0.2.0/dsh_openai_shim/discover.py +303 -0
- dsh_openai_shim-0.2.0/dsh_openai_shim/proxy_cli.py +387 -0
- dsh_openai_shim-0.2.0/dsh_openai_shim/runtime.py +199 -0
- dsh_openai_shim-0.2.0/pyproject.toml +39 -0
- dsh_openai_shim-0.2.0/tests/test_config.py +318 -0
- dsh_openai_shim-0.2.0/tests/test_discover.py +443 -0
- dsh_openai_shim-0.2.0/tests/test_proxy.py +228 -0
- dsh_openai_shim-0.2.0/tests/test_shim.py +290 -0
- dsh_openai_shim-0.2.0/uv.lock +155 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# dsh-openai-shim Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `dsh-openai-shim` package are documented here.
|
|
4
|
+
This project follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [0.2.0] — 2026-09-21
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Context-window-aware `max_tokens` (fixes the sglang/vLLM "context length" 400).**
|
|
10
|
+
Providers such as sglang/vLLM report `max_model_len` as the *total* context
|
|
11
|
+
window (input + output), not the output cap. A non-trivial prompt plus a large
|
|
12
|
+
`max_tokens` overflowed the window and 400'd with
|
|
13
|
+
*"exceeds the model's maximum context length of N tokens. You requested a
|
|
14
|
+
total of X tokens: I tokens from the input messages and N tokens for
|
|
15
|
+
completion."* This version handles that in two layers:
|
|
16
|
+
- **Proactive clamp** — `proxy.conf` now stores both `token_cap` (output) and
|
|
17
|
+
`context_window` (total); the shim clamps
|
|
18
|
+
`max_tokens = min(token_cap, context_window − est_input − 128)` so a long
|
|
19
|
+
prompt never overflows. `dsh-proxy use --base` auto-discovers **both**
|
|
20
|
+
limits from `/v1/models`.
|
|
21
|
+
- **Self-heal backstop** — if a context-window 400 still occurs, the shim
|
|
22
|
+
parses the server's *exact* input-token count from the error, retries once
|
|
23
|
+
with `context_window − input − 128`, and persists the discovered
|
|
24
|
+
`context_window` so the next request clamps proactively (no further 400).
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- `resolve_upstream` / `ShimConfig` now carry the context window alongside the
|
|
28
|
+
output cap (4-tuple resolution). The build-time deployment-agnostic gate is
|
|
29
|
+
updated for the 4-tuple.
|
|
30
|
+
- Provider discovery reports both `token_cap` and `context_window`
|
|
31
|
+
(`discover_model_limits`); `discover_completion_cap` remains as a
|
|
32
|
+
back-compatible wrapper.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Requests with a large prompt + a large `max_tokens` no longer fail against
|
|
36
|
+
sglang/vLLM endpoints (the second, context-window 400 that the 0.1.x
|
|
37
|
+
output-cap-only self-heal did not catch).
|
|
38
|
+
|
|
39
|
+
## [0.1.1] — 2026-09
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- **Per-provider completion-token cap, auto-discovered.** `dsh-proxy use --base`
|
|
43
|
+
probes `/v1/models` to learn the upstream's output limit and stores it as the
|
|
44
|
+
provider's `token_cap`; the shim clamps `max_tokens` to it and self-heals on a
|
|
45
|
+
*completion-cap* 400 (parses the server-reported cap, retries, persists).
|
|
46
|
+
- **`dsh-proxy use` restarts the shim** (was stop-only, leaving a dead endpoint).
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- `dsh-proxy status` exit code and the `serve` pidfile handling (two
|
|
50
|
+
boot-breaking bugs).
|
|
51
|
+
- Default provider is now `litellm` (DiveAI 404s on `/chat/completions` and is
|
|
52
|
+
not a valid default).
|
|
53
|
+
|
|
54
|
+
## [0.1.0] — 2026-09
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- Initial release: a tiny, **zero-dependency (stdlib-only)** OpenAI-compatible
|
|
58
|
+
proxy that adapts DeepSeek Harness (dsh) to any OpenAI-compatible endpoint
|
|
59
|
+
(Socrates / LiteLLM / any sglang/vLLM endpoint).
|
|
60
|
+
- Safe request rewrites: `reasoning_effort` (remap to a supported value or drop)
|
|
61
|
+
and `max_tokens` (clamp).
|
|
62
|
+
- `dsh-openai-shim` (serve) and `dsh-proxy` (use/list/show/status/restart/sync)
|
|
63
|
+
command-line entry points.
|
|
64
|
+
|
|
65
|
+
[0.2.0]: https://github.com/dive4dec/dsh-openai-shim/compare/v0.1.1...v0.2.0
|
|
66
|
+
[0.1.1]: https://github.com/dive4dec/dsh-openai-shim/compare/v0.1.0...v0.1.1
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dive4dec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: dsh-openai-shim
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Zero-dependency OpenAI-compatible proxy that adapts DeepSeek Harness (dsh) to any OpenAI-compatible endpoint (Socrates/LiteLLM/sglang/vLLM)
|
|
5
|
+
Project-URL: Homepage, https://github.com/dive4dec/dsh-openai-shim
|
|
6
|
+
Project-URL: Source Code, https://github.com/dive4dec/dsh-openai-shim
|
|
7
|
+
Author: dive4dec
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: deepseek,dsh,harness,litellm,openai,proxy,reasoning,sglang,vllm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# dsh-openai-shim
|
|
26
|
+
|
|
27
|
+
A **zero-dependency, stdlib-only** OpenAI-compatible proxy that adapts
|
|
28
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) to
|
|
29
|
+
**any** OpenAI-compatible endpoint — Socrates, LiteLLM, sglang, vLLM, etc.
|
|
30
|
+
|
|
31
|
+
## Why
|
|
32
|
+
|
|
33
|
+
`dsh`'s bundled `deepseek-official` provider is written for the DeepSeek API and
|
|
34
|
+
does two things that break against general OpenAI endpoints like Socrates:
|
|
35
|
+
|
|
36
|
+
1. It **hard-injects `reasoning_effort: "high"`** into every request. Socrates
|
|
37
|
+
(sglang) rejects `"high"` — it only accepts `xhigh` / `medium` / `low`.
|
|
38
|
+
→ `HTTP 400: Unexpected reasoning effort high.`
|
|
39
|
+
2. It requests a **default `max_tokens` of 256000**, which — combined with a
|
|
40
|
+
large system prompt — can exceed the model's context window.
|
|
41
|
+
→ `HTTP 400: Requested token count exceeds the model's maximum context length.`
|
|
42
|
+
|
|
43
|
+
Both are *request-parameter* mismatches, not protocol mismatches. `dsh` is not
|
|
44
|
+
DeepSeek-locked; it connects, authenticates, routes, and runs its full agent
|
|
45
|
+
tool loop against any OpenAI endpoint. The only friction is these two params.
|
|
46
|
+
|
|
47
|
+
Rather than fight dsh's `cordis.patch.yml` / model-catalog (dev-preview,
|
|
48
|
+
compiled into a native binary, version-churny), this shim is the **single
|
|
49
|
+
compatibility chokepoint**: a tiny proxy dsh points at, which forwards to your
|
|
50
|
+
endpoint and rewrites the two params. It is:
|
|
51
|
+
|
|
52
|
+
- **agent-agnostic** — works for every dsh profile (`sdk`, `sdk-minimal`,
|
|
53
|
+
`acp`, `web`) and any OpenAI client.
|
|
54
|
+
- **version-immune** — if dsh changes its defaults, the shim still fixes them.
|
|
55
|
+
- **transparent** — passes through auth headers, streaming, `/models`,
|
|
56
|
+
tool-calling, everything else unchanged.
|
|
57
|
+
|
|
58
|
+
## What it rewrites
|
|
59
|
+
|
|
60
|
+
| Param | Default behavior | Options |
|
|
61
|
+
|-------|------------------|---------|
|
|
62
|
+
| `reasoning_effort` | `high → medium` (map to a supported value) | `--effort-mode map` (default) / `drop` (remove it) / `off` |
|
|
63
|
+
| `max_tokens` | clamp to the provider's `token_cap` (env default `DSH_SHIM_TOKEN_CAP`, `100000`) | per-provider cap (auto-discovered); `0` disables clamping |
|
|
64
|
+
|
|
65
|
+
The `max_tokens` cap is **per-provider**, not global: different models have
|
|
66
|
+
different completion ceilings (a local sglang/vLLM server may allow 64000, a
|
|
67
|
+
big hosted endpoint 100000+). The cap is discovered automatically when a
|
|
68
|
+
provider is added, corrected automatically if the upstream rejects it, and can
|
|
69
|
+
always be overridden with `--token-cap` — see [Per-user providers](#per-user-providers-dsh-proxy).
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
pip install dsh-openai-shim
|
|
75
|
+
# or from source
|
|
76
|
+
pip install -e .
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
No third-party runtime dependencies — pure Python stdlib.
|
|
80
|
+
|
|
81
|
+
## Use
|
|
82
|
+
|
|
83
|
+
Run the shim, then point dsh at it:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
# 1. start the shim (maps high→medium, caps max_tokens at 100000)
|
|
87
|
+
dsh-openai-shim serve \
|
|
88
|
+
--upstream https://socratic.cs.cityu.edu.hk/ai-test \
|
|
89
|
+
--port 8090
|
|
90
|
+
|
|
91
|
+
# 2. point dsh at the shim (not the upstream directly)
|
|
92
|
+
DSH_HOME=/tmp/dsh python - <<'PY'
|
|
93
|
+
from deepseek_harness import DeepSeekHarness
|
|
94
|
+
with DeepSeekHarness(
|
|
95
|
+
dsh_home="/tmp/dsh",
|
|
96
|
+
cwd="/workspace",
|
|
97
|
+
provider="deepseek-official",
|
|
98
|
+
model="Socrates", # your endpoint's model id
|
|
99
|
+
base_url="http://127.0.0.1:8090/v1", # NOTE: points at the shim
|
|
100
|
+
api_key="...",
|
|
101
|
+
max_tokens=100000,
|
|
102
|
+
) as h:
|
|
103
|
+
print(h.run("Say hi.", session_id="s").final_response)
|
|
104
|
+
PY
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> **Path note:** if the client appends `/v1/...` to `base_url`, set
|
|
108
|
+
> `--upstream` to the path **without** a trailing `/v1`.
|
|
109
|
+
|
|
110
|
+
### Useful flags
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
--effort-mode {map,drop,off} how to handle reasoning_effort (default: map)
|
|
114
|
+
--token-cap N clamp completion max_tokens to N (default: 100000, 0 = off)
|
|
115
|
+
--upstream-key Bearer force a specific Authorization key upstream
|
|
116
|
+
--host / --port listen address (default 127.0.0.1:8090)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### One-shot rewrite (no server) — for tests / debugging
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
echo '{"reasoning_effort":"high","max_tokens":256000}' | \
|
|
123
|
+
dsh-openai-shim rewrite --effort-mode map
|
|
124
|
+
# {"reasoning_effort": "medium", "max_tokens": 100000}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Per-user providers (`dsh-proxy`)
|
|
128
|
+
|
|
129
|
+
Beyond the one-shot `dsh-openai-shim serve --upstream …`, the package ships a
|
|
130
|
+
**per-user provider manager** (`dsh-proxy`) for deployments that run one pod
|
|
131
|
+
per student (e.g. JupyterHub). The student's choice of upstream is persisted
|
|
132
|
+
in `~/.dsh/proxy.conf` (NFS home) so it survives pod restarts — the same
|
|
133
|
+
file-based model as Hermes' `~/.hermes/config.yaml`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"provider": "litellm",
|
|
138
|
+
"providers": {
|
|
139
|
+
"diveai": {"base_url": "https://…/ai/v1", "api_key": "…"},
|
|
140
|
+
"litellm": {"base_url": "https://…/litellm/v1", "api_key": "…"},
|
|
141
|
+
"myprov": {"base_url": "http://10.0.0.5:30000/v1", "api_key": "…",
|
|
142
|
+
"token_cap": 64000}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- **The deployment's providers** (e.g. `diveai`, `litellm`) are upserted by
|
|
148
|
+
`dsh-proxy sync` on every notebook start — it reads the endpoint + key from
|
|
149
|
+
the pod environment (`DIVEAI_API_BASE/_KEY`, `LITELLM_API_BASE/_KEY`) and
|
|
150
|
+
refreshes those entries (key rotation, endpoint moves), without touching
|
|
151
|
+
student-added providers. The first-run default provider is set once and
|
|
152
|
+
never clobbered afterwards. A student-set `token_cap` survives a re-sync.
|
|
153
|
+
- **Custom providers** are the student's own: `dsh-proxy use <name> --base
|
|
154
|
+
<url> [--key <key>]` adds ANY name (as many as you like) and switches to
|
|
155
|
+
it. Omit `--key` to forward the caller's `Authorization` header as-is.
|
|
156
|
+
- **Completion-token cap is auto-discovered** (the student never has to know
|
|
157
|
+
the model's limit). On `use <name> --base <url>`, the shim probes
|
|
158
|
+
`GET /v1/models` on the new endpoint and reads the model's completion ceiling
|
|
159
|
+
(`max_completion_tokens` / `max_model_len`) into `token_cap`. This is the same
|
|
160
|
+
technique Hermes uses when you add a custom endpoint and skip the context
|
|
161
|
+
length. If the probe can't reach the endpoint (or it doesn't advertise a
|
|
162
|
+
limit), the cap is left at the env default — and the shim **self-heals at
|
|
163
|
+
request time**: if the upstream rejects a request with an "N too large … at
|
|
164
|
+
most M tokens" 400, the shim parses `M`, lowers the cap, retries the request
|
|
165
|
+
once, and persists `M` to proxy.conf. So a wrong/missing cap corrects itself
|
|
166
|
+
on the first failed turn. Override either step with `--token-cap N`
|
|
167
|
+
(or `--no-discover` to skip the probe entirely).
|
|
168
|
+
- **Resolution is file-based and env-inert** — the daemon reads only
|
|
169
|
+
proxy.conf. A trailing `/v1` on a base is stripped automatically (the shim
|
|
170
|
+
re-appends it when forwarding).
|
|
171
|
+
|
|
172
|
+
```sh
|
|
173
|
+
dsh-proxy list # all providers, current flagged (*), caps shown
|
|
174
|
+
dsh-proxy use litellm # switch to an existing provider
|
|
175
|
+
dsh-proxy use myprov --base https://host/v1 --key sk-... # add + switch (cap auto-detected)
|
|
176
|
+
dsh-proxy use spark --base http://10.0.0.5:30000/v1 --key sk-... # cap auto-detected
|
|
177
|
+
dsh-proxy use myprov --base https://host/v1 --key sk-... --token-cap 40000 # force a cap
|
|
178
|
+
dsh-proxy show # current provider + upstream + effective cap (key masked)
|
|
179
|
+
dsh-proxy sync # (in-pod) refresh deployment providers
|
|
180
|
+
dsh-proxy status # is the shim daemon running?
|
|
181
|
+
dsh-proxy restart # restart the shim daemon from proxy.conf
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`use` writes the file and then **restarts** the running shim so the new
|
|
185
|
+
provider takes effect immediately (there is no lazy auto-start in the request
|
|
186
|
+
path — dsh makes plain HTTP calls to the loopback port). `sync` never
|
|
187
|
+
restarts anything; the caller (the pod startup hook) restarts the daemon
|
|
188
|
+
only when `sync` prints `changed=yes`.
|
|
189
|
+
|
|
190
|
+
The package itself is **deployment-agnostic**: no provider catalog, no
|
|
191
|
+
endpoints, no env-var names beyond the two raw `DIVEAI_*` / `LITELLM_*` pair
|
|
192
|
+
the sync reads. A build-time gate (`dsh_shim_deployment_agnostic_gate.py` in
|
|
193
|
+
the Jupyter image) fails the build if a deployment host ever leaks into the
|
|
194
|
+
installed package or if resolution starts reading the environment.
|
|
195
|
+
|
|
196
|
+
## Library use
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from dsh_openai_shim import ShimConfig, apply_rewrites, make_handler, serve
|
|
200
|
+
|
|
201
|
+
cfg = ShimConfig(upstream="http://127.0.0.1:9999/v1",
|
|
202
|
+
effort_mode="map", token_cap=100000)
|
|
203
|
+
new_body, changes = apply_rewrites(body, cfg) # pure, testable
|
|
204
|
+
serve(cfg) # run the server
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Security
|
|
208
|
+
|
|
209
|
+
- By default the shim **forwards the client's `Authorization` header** to the
|
|
210
|
+
upstream. Use `--upstream-key` to replace it with a server-side key so the
|
|
211
|
+
client never holds the real endpoint credential.
|
|
212
|
+
- Bind to `127.0.0.1` (default). Do not expose the shim to untrusted networks
|
|
213
|
+
without auth.
|
|
214
|
+
|
|
215
|
+
## Tests
|
|
216
|
+
|
|
217
|
+
```sh
|
|
218
|
+
python -m pytest tests/ -q
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The end-to-end tests run a local fake OpenAI upstream (no external network, no
|
|
222
|
+
dsh required) and assert the shim rewrites `reasoning_effort` and `max_tokens`
|
|
223
|
+
while passing through auth and the response.
|
|
224
|
+
|
|
225
|
+
## Status
|
|
226
|
+
|
|
227
|
+
v0.1.0 — spike-proven against Socrates (dsh → shim → Socrates → `pong`).
|
|
228
|
+
Zero deps, MIT.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# dsh-openai-shim
|
|
2
|
+
|
|
3
|
+
A **zero-dependency, stdlib-only** OpenAI-compatible proxy that adapts
|
|
4
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) to
|
|
5
|
+
**any** OpenAI-compatible endpoint — Socrates, LiteLLM, sglang, vLLM, etc.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
`dsh`'s bundled `deepseek-official` provider is written for the DeepSeek API and
|
|
10
|
+
does two things that break against general OpenAI endpoints like Socrates:
|
|
11
|
+
|
|
12
|
+
1. It **hard-injects `reasoning_effort: "high"`** into every request. Socrates
|
|
13
|
+
(sglang) rejects `"high"` — it only accepts `xhigh` / `medium` / `low`.
|
|
14
|
+
→ `HTTP 400: Unexpected reasoning effort high.`
|
|
15
|
+
2. It requests a **default `max_tokens` of 256000**, which — combined with a
|
|
16
|
+
large system prompt — can exceed the model's context window.
|
|
17
|
+
→ `HTTP 400: Requested token count exceeds the model's maximum context length.`
|
|
18
|
+
|
|
19
|
+
Both are *request-parameter* mismatches, not protocol mismatches. `dsh` is not
|
|
20
|
+
DeepSeek-locked; it connects, authenticates, routes, and runs its full agent
|
|
21
|
+
tool loop against any OpenAI endpoint. The only friction is these two params.
|
|
22
|
+
|
|
23
|
+
Rather than fight dsh's `cordis.patch.yml` / model-catalog (dev-preview,
|
|
24
|
+
compiled into a native binary, version-churny), this shim is the **single
|
|
25
|
+
compatibility chokepoint**: a tiny proxy dsh points at, which forwards to your
|
|
26
|
+
endpoint and rewrites the two params. It is:
|
|
27
|
+
|
|
28
|
+
- **agent-agnostic** — works for every dsh profile (`sdk`, `sdk-minimal`,
|
|
29
|
+
`acp`, `web`) and any OpenAI client.
|
|
30
|
+
- **version-immune** — if dsh changes its defaults, the shim still fixes them.
|
|
31
|
+
- **transparent** — passes through auth headers, streaming, `/models`,
|
|
32
|
+
tool-calling, everything else unchanged.
|
|
33
|
+
|
|
34
|
+
## What it rewrites
|
|
35
|
+
|
|
36
|
+
| Param | Default behavior | Options |
|
|
37
|
+
|-------|------------------|---------|
|
|
38
|
+
| `reasoning_effort` | `high → medium` (map to a supported value) | `--effort-mode map` (default) / `drop` (remove it) / `off` |
|
|
39
|
+
| `max_tokens` | clamp to the provider's `token_cap` (env default `DSH_SHIM_TOKEN_CAP`, `100000`) | per-provider cap (auto-discovered); `0` disables clamping |
|
|
40
|
+
|
|
41
|
+
The `max_tokens` cap is **per-provider**, not global: different models have
|
|
42
|
+
different completion ceilings (a local sglang/vLLM server may allow 64000, a
|
|
43
|
+
big hosted endpoint 100000+). The cap is discovered automatically when a
|
|
44
|
+
provider is added, corrected automatically if the upstream rejects it, and can
|
|
45
|
+
always be overridden with `--token-cap` — see [Per-user providers](#per-user-providers-dsh-proxy).
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pip install dsh-openai-shim
|
|
51
|
+
# or from source
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
No third-party runtime dependencies — pure Python stdlib.
|
|
56
|
+
|
|
57
|
+
## Use
|
|
58
|
+
|
|
59
|
+
Run the shim, then point dsh at it:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# 1. start the shim (maps high→medium, caps max_tokens at 100000)
|
|
63
|
+
dsh-openai-shim serve \
|
|
64
|
+
--upstream https://socratic.cs.cityu.edu.hk/ai-test \
|
|
65
|
+
--port 8090
|
|
66
|
+
|
|
67
|
+
# 2. point dsh at the shim (not the upstream directly)
|
|
68
|
+
DSH_HOME=/tmp/dsh python - <<'PY'
|
|
69
|
+
from deepseek_harness import DeepSeekHarness
|
|
70
|
+
with DeepSeekHarness(
|
|
71
|
+
dsh_home="/tmp/dsh",
|
|
72
|
+
cwd="/workspace",
|
|
73
|
+
provider="deepseek-official",
|
|
74
|
+
model="Socrates", # your endpoint's model id
|
|
75
|
+
base_url="http://127.0.0.1:8090/v1", # NOTE: points at the shim
|
|
76
|
+
api_key="...",
|
|
77
|
+
max_tokens=100000,
|
|
78
|
+
) as h:
|
|
79
|
+
print(h.run("Say hi.", session_id="s").final_response)
|
|
80
|
+
PY
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
> **Path note:** if the client appends `/v1/...` to `base_url`, set
|
|
84
|
+
> `--upstream` to the path **without** a trailing `/v1`.
|
|
85
|
+
|
|
86
|
+
### Useful flags
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
--effort-mode {map,drop,off} how to handle reasoning_effort (default: map)
|
|
90
|
+
--token-cap N clamp completion max_tokens to N (default: 100000, 0 = off)
|
|
91
|
+
--upstream-key Bearer force a specific Authorization key upstream
|
|
92
|
+
--host / --port listen address (default 127.0.0.1:8090)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### One-shot rewrite (no server) — for tests / debugging
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
echo '{"reasoning_effort":"high","max_tokens":256000}' | \
|
|
99
|
+
dsh-openai-shim rewrite --effort-mode map
|
|
100
|
+
# {"reasoning_effort": "medium", "max_tokens": 100000}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Per-user providers (`dsh-proxy`)
|
|
104
|
+
|
|
105
|
+
Beyond the one-shot `dsh-openai-shim serve --upstream …`, the package ships a
|
|
106
|
+
**per-user provider manager** (`dsh-proxy`) for deployments that run one pod
|
|
107
|
+
per student (e.g. JupyterHub). The student's choice of upstream is persisted
|
|
108
|
+
in `~/.dsh/proxy.conf` (NFS home) so it survives pod restarts — the same
|
|
109
|
+
file-based model as Hermes' `~/.hermes/config.yaml`:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"provider": "litellm",
|
|
114
|
+
"providers": {
|
|
115
|
+
"diveai": {"base_url": "https://…/ai/v1", "api_key": "…"},
|
|
116
|
+
"litellm": {"base_url": "https://…/litellm/v1", "api_key": "…"},
|
|
117
|
+
"myprov": {"base_url": "http://10.0.0.5:30000/v1", "api_key": "…",
|
|
118
|
+
"token_cap": 64000}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
- **The deployment's providers** (e.g. `diveai`, `litellm`) are upserted by
|
|
124
|
+
`dsh-proxy sync` on every notebook start — it reads the endpoint + key from
|
|
125
|
+
the pod environment (`DIVEAI_API_BASE/_KEY`, `LITELLM_API_BASE/_KEY`) and
|
|
126
|
+
refreshes those entries (key rotation, endpoint moves), without touching
|
|
127
|
+
student-added providers. The first-run default provider is set once and
|
|
128
|
+
never clobbered afterwards. A student-set `token_cap` survives a re-sync.
|
|
129
|
+
- **Custom providers** are the student's own: `dsh-proxy use <name> --base
|
|
130
|
+
<url> [--key <key>]` adds ANY name (as many as you like) and switches to
|
|
131
|
+
it. Omit `--key` to forward the caller's `Authorization` header as-is.
|
|
132
|
+
- **Completion-token cap is auto-discovered** (the student never has to know
|
|
133
|
+
the model's limit). On `use <name> --base <url>`, the shim probes
|
|
134
|
+
`GET /v1/models` on the new endpoint and reads the model's completion ceiling
|
|
135
|
+
(`max_completion_tokens` / `max_model_len`) into `token_cap`. This is the same
|
|
136
|
+
technique Hermes uses when you add a custom endpoint and skip the context
|
|
137
|
+
length. If the probe can't reach the endpoint (or it doesn't advertise a
|
|
138
|
+
limit), the cap is left at the env default — and the shim **self-heals at
|
|
139
|
+
request time**: if the upstream rejects a request with an "N too large … at
|
|
140
|
+
most M tokens" 400, the shim parses `M`, lowers the cap, retries the request
|
|
141
|
+
once, and persists `M` to proxy.conf. So a wrong/missing cap corrects itself
|
|
142
|
+
on the first failed turn. Override either step with `--token-cap N`
|
|
143
|
+
(or `--no-discover` to skip the probe entirely).
|
|
144
|
+
- **Resolution is file-based and env-inert** — the daemon reads only
|
|
145
|
+
proxy.conf. A trailing `/v1` on a base is stripped automatically (the shim
|
|
146
|
+
re-appends it when forwarding).
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
dsh-proxy list # all providers, current flagged (*), caps shown
|
|
150
|
+
dsh-proxy use litellm # switch to an existing provider
|
|
151
|
+
dsh-proxy use myprov --base https://host/v1 --key sk-... # add + switch (cap auto-detected)
|
|
152
|
+
dsh-proxy use spark --base http://10.0.0.5:30000/v1 --key sk-... # cap auto-detected
|
|
153
|
+
dsh-proxy use myprov --base https://host/v1 --key sk-... --token-cap 40000 # force a cap
|
|
154
|
+
dsh-proxy show # current provider + upstream + effective cap (key masked)
|
|
155
|
+
dsh-proxy sync # (in-pod) refresh deployment providers
|
|
156
|
+
dsh-proxy status # is the shim daemon running?
|
|
157
|
+
dsh-proxy restart # restart the shim daemon from proxy.conf
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`use` writes the file and then **restarts** the running shim so the new
|
|
161
|
+
provider takes effect immediately (there is no lazy auto-start in the request
|
|
162
|
+
path — dsh makes plain HTTP calls to the loopback port). `sync` never
|
|
163
|
+
restarts anything; the caller (the pod startup hook) restarts the daemon
|
|
164
|
+
only when `sync` prints `changed=yes`.
|
|
165
|
+
|
|
166
|
+
The package itself is **deployment-agnostic**: no provider catalog, no
|
|
167
|
+
endpoints, no env-var names beyond the two raw `DIVEAI_*` / `LITELLM_*` pair
|
|
168
|
+
the sync reads. A build-time gate (`dsh_shim_deployment_agnostic_gate.py` in
|
|
169
|
+
the Jupyter image) fails the build if a deployment host ever leaks into the
|
|
170
|
+
installed package or if resolution starts reading the environment.
|
|
171
|
+
|
|
172
|
+
## Library use
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from dsh_openai_shim import ShimConfig, apply_rewrites, make_handler, serve
|
|
176
|
+
|
|
177
|
+
cfg = ShimConfig(upstream="http://127.0.0.1:9999/v1",
|
|
178
|
+
effort_mode="map", token_cap=100000)
|
|
179
|
+
new_body, changes = apply_rewrites(body, cfg) # pure, testable
|
|
180
|
+
serve(cfg) # run the server
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Security
|
|
184
|
+
|
|
185
|
+
- By default the shim **forwards the client's `Authorization` header** to the
|
|
186
|
+
upstream. Use `--upstream-key` to replace it with a server-side key so the
|
|
187
|
+
client never holds the real endpoint credential.
|
|
188
|
+
- Bind to `127.0.0.1` (default). Do not expose the shim to untrusted networks
|
|
189
|
+
without auth.
|
|
190
|
+
|
|
191
|
+
## Tests
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
python -m pytest tests/ -q
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The end-to-end tests run a local fake OpenAI upstream (no external network, no
|
|
198
|
+
dsh required) and assert the shim rewrites `reasoning_effort` and `max_tokens`
|
|
199
|
+
while passing through auth and the response.
|
|
200
|
+
|
|
201
|
+
## Status
|
|
202
|
+
|
|
203
|
+
v0.1.0 — spike-proven against Socrates (dsh → shim → Socrates → `pong`).
|
|
204
|
+
Zero deps, MIT.
|