Skip to content

Commit e31241b

Browse files
apartsinclaude
andcommitted
Add OpenAI proxy Docker deployment, docs, automation scripts, and 125 tests
- Add modelmesh.yaml multi-provider config (OpenAI, Anthropic, Groq) - Patch Dockerfile to install pyyaml for YAML config loading - Add .env.example template for API keys - Create docs/guides/ProxyGuide.md with full proxy documentation: configuration, CLI reference, REST API endpoints, Docker deployment, browser usage, OpenAI SDK compatibility, and troubleshooting - Create samples/proxy-test/index.html — self-contained vanilla JS browser test page with model listing, streaming, and non-streaming chat - Add 7 automation scripts: proxy-up, proxy-down, proxy-test, docker-build, install-python, install-typescript, test-all - Add .github/workflows/publish.yml for PyPI, npm, and GHCR publishing - Expand tests.yml CI to include TypeScript and Docker build jobs - Add tests/test_docker.py (80 Python tests): Dockerfile, docker-compose, config, scripts, browser page, proxy module, CLI, live HTTP integration - Add 45 TypeScript tests for Docker infrastructure validation - Update README with proxy guide, scripts table, Docker quick-start - Update badge and counts: 855 Python + 511 TypeScript = 1,366 tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f74737a commit e31241b

File tree

20 files changed

+2644
-11
lines changed

20 files changed

+2644
-11
lines changed

.env.example

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# ModelMesh proxy — API keys
2+
# Copy this file to .env and fill in your keys.
3+
# .env is gitignored and never committed.
4+
5+
# Required: at least one provider key
6+
OPENAI_API_KEY=
7+
ANTHROPIC_API_KEY=
8+
GROQ_API_KEY=
9+
10+
# Optional: additional providers
11+
# GOOGLE_API_KEY=
12+
# DEEPSEEK_API_KEY=
13+
# MISTRAL_API_KEY=
14+
# TOGETHER_API_KEY=
15+
# OPENROUTER_API_KEY=
16+
# XAI_API_KEY=
17+
# COHERE_API_KEY=

.github/workflows/publish.yml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: Publish
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
inputs:
8+
target:
9+
description: "Publish target"
10+
required: true
11+
type: choice
12+
options:
13+
- all
14+
- pypi
15+
- npm
16+
- docker
17+
18+
permissions:
19+
contents: read
20+
packages: write
21+
22+
jobs:
23+
# ── Python → PyPI ──────────────────────────────────────────────────────
24+
pypi:
25+
if: >-
26+
github.event_name == 'release' ||
27+
(github.event_name == 'workflow_dispatch' && (github.event.inputs.target == 'all' || github.event.inputs.target == 'pypi'))
28+
runs-on: ubuntu-latest
29+
steps:
30+
- uses: actions/checkout@v4
31+
- uses: actions/setup-python@v5
32+
with:
33+
python-version: "3.12"
34+
- name: Install build tools
35+
run: pip install build twine
36+
- name: Build package
37+
run: python -m build
38+
- name: Publish to PyPI
39+
env:
40+
TWINE_USERNAME: __token__
41+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
42+
run: twine upload dist/*
43+
44+
# ── TypeScript → npm ───────────────────────────────────────────────────
45+
npm:
46+
if: >-
47+
github.event_name == 'release' ||
48+
(github.event_name == 'workflow_dispatch' && (github.event.inputs.target == 'all' || github.event.inputs.target == 'npm'))
49+
runs-on: ubuntu-latest
50+
steps:
51+
- uses: actions/checkout@v4
52+
- uses: actions/setup-node@v4
53+
with:
54+
node-version: "20"
55+
registry-url: "https://registry.npmjs.org"
56+
- name: Install dependencies
57+
working-directory: src/typescript
58+
run: npm ci
59+
- name: Build
60+
working-directory: src/typescript
61+
run: npm run build --if-present
62+
- name: Publish to npm
63+
working-directory: src/typescript
64+
env:
65+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
66+
run: npm publish --access public
67+
68+
# ── Docker → GitHub Container Registry ─────────────────────────────────
69+
docker:
70+
if: >-
71+
github.event_name == 'release' ||
72+
(github.event_name == 'workflow_dispatch' && (github.event.inputs.target == 'all' || github.event.inputs.target == 'docker'))
73+
runs-on: ubuntu-latest
74+
steps:
75+
- uses: actions/checkout@v4
76+
- name: Set up Docker Buildx
77+
uses: docker/setup-buildx-action@v3
78+
- name: Log in to GHCR
79+
uses: docker/login-action@v3
80+
with:
81+
registry: ghcr.io
82+
username: ${{ github.actor }}
83+
password: ${{ secrets.GITHUB_TOKEN }}
84+
- name: Extract version
85+
id: version
86+
run: echo "tag=${GITHUB_REF_NAME:-latest}" >> $GITHUB_OUTPUT
87+
- name: Build and push
88+
uses: docker/build-push-action@v5
89+
with:
90+
context: .
91+
push: true
92+
tags: |
93+
ghcr.io/${{ github.repository_owner }}/modelmesh:${{ steps.version.outputs.tag }}
94+
ghcr.io/${{ github.repository_owner }}/modelmesh:latest
95+
cache-from: type=gha
96+
cache-to: type=gha,mode=max

.github/workflows/tests.yml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ on:
66
branches: [master]
77

88
jobs:
9-
test:
9+
python:
10+
name: Python ${{ matrix.python-version }}
1011
runs-on: ubuntu-latest
1112
strategy:
1213
matrix:
@@ -18,3 +19,27 @@ jobs:
1819
python-version: ${{ matrix.python-version }}
1920
- run: pip install pytest pytest-asyncio
2021
- run: cd src/python && python -m pytest ../../tests/ -v
22+
23+
typescript:
24+
name: TypeScript (Node ${{ matrix.node-version }})
25+
runs-on: ubuntu-latest
26+
strategy:
27+
matrix:
28+
node-version: ["18", "20"]
29+
steps:
30+
- uses: actions/checkout@v4
31+
- uses: actions/setup-node@v4
32+
with:
33+
node-version: ${{ matrix.node-version }}
34+
- run: cd src/typescript && npm ci && npm test
35+
36+
docker:
37+
name: Docker Build
38+
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@v4
41+
- name: Build Docker image
42+
run: docker build -t modelmesh-proxy:test .
43+
- name: Verify image runs
44+
run: |
45+
docker run --rm modelmesh-proxy:test --help 2>&1 || true

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Thumbs.db
2727
# Environment
2828
.env
2929
.env.*
30+
!.env.example
3031

3132
# Logs
3233
*.log
@@ -35,6 +36,9 @@ Thumbs.db
3536
node_modules/
3637
*.tgz
3738

39+
# Test output
40+
jest-out.json
41+
3842
# Package
3943
*.whl
4044
*.tar.gz

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM python:3.12-slim
22
WORKDIR /app
33
COPY src/python/ ./
44
COPY pyproject.toml ./
5-
RUN pip install .
5+
RUN pip install . && pip install pyyaml>=6.0
66
EXPOSE 8080
77
ENTRYPOINT ["python", "-m", "modelmesh.proxy"]
88
CMD ["--host", "0.0.0.0", "--port", "8080"]

README.md

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<img src="https://img.shields.io/badge/typescript-5.0%2B-blue" alt="TypeScript 5.0+">
1313
<img src="https://img.shields.io/badge/docker-supported-2496ED" alt="Docker">
1414
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
15-
<a href="https://github.com/ApartsinProjects/ModelMesh/actions"><img src="https://img.shields.io/badge/tests-1%2C241%20passed-brightgreen" alt="Tests"></a>
15+
<a href="https://github.com/ApartsinProjects/ModelMesh/actions"><img src="https://img.shields.io/badge/tests-1%2C366%20passed-brightgreen" alt="Tests"></a>
1616
<a href="https://apartsinprojects.github.io/ModelMesh/"><img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue" alt="Documentation"></a>
1717
</p>
1818

@@ -170,6 +170,7 @@ client = modelmesh.create(config="modelmesh.yaml")
170170
| **[Connector Catalogue](docs/ConnectorCatalogue.md)** | All pre-shipped connectors with config schemas |
171171
| **[Connector Interfaces](docs/ConnectorInterfaces.md)** | Interface definitions for all connector types |
172172
| **[System Services](docs/SystemServices.md)** | Runtime objects: Router, Pool, Model, State |
173+
| **[Proxy Guide](docs/guides/ProxyGuide.md)** | Deploy as OpenAI-compatible proxy: Docker, CLI, config, browser access |
173174

174175
### CDK (Connector Development Kit)
175176

@@ -189,6 +190,7 @@ client = modelmesh.create(config="modelmesh.yaml")
189190
| **[System Integration](samples/system/)** | Multi-provider, streaming, embeddings, cost optimization |
190191
| **[CDK Tutorials](samples/cdk/)** | Build providers, rotation policies, and more |
191192
| **[Custom Connectors](samples/connectors/)** | Full custom connector examples for all 6 types |
193+
| **[Proxy Test](samples/proxy-test/)** | Vanilla JS browser test page for the OpenAI proxy |
192194

193195
## Development
194196

@@ -197,20 +199,48 @@ client = modelmesh.create(config="modelmesh.yaml")
197199
git clone https://github.com/ApartsinProjects/ModelMesh.git
198200
cd ModelMesh
199201

200-
# Run Python tests (775 tests)
202+
# Run Python tests (855 tests)
201203
pip install pytest
202204
cd src/python && python -m pytest ../../tests/ -v
203205

204-
# Run TypeScript tests (466 tests)
206+
# Run TypeScript tests (511 tests)
205207
cd src/typescript && npm install && npm test
208+
209+
# Or use the automation script
210+
./scripts/test-all.sh
206211
```
207212

208213
## Docker
209214

210215
```bash
211-
docker run -e OPENAI_API_KEY="sk-..." ghcr.io/apartsinprojects/modelmesh
216+
# Quick start with Docker Compose
217+
cp .env.example .env # then add your API keys
218+
docker compose up --build
219+
220+
# Or use the automation script
221+
./scripts/proxy-up.sh
222+
223+
# Test the running proxy
224+
curl http://localhost:8080/v1/models
225+
curl -X POST http://localhost:8080/v1/chat/completions \
226+
-H "Content-Type: application/json" \
227+
-d '{"model":"text-generation","messages":[{"role":"user","content":"Hello!"}]}'
212228
```
213229

230+
See the **[Proxy Guide](docs/guides/ProxyGuide.md)** for full configuration, CLI reference, and browser access.
231+
232+
## Scripts
233+
234+
| Script | Description |
235+
|---|---|
236+
| `scripts/proxy-up.sh` | Build and start the Docker proxy |
237+
| `scripts/proxy-down.sh` | Stop the Docker proxy |
238+
| `scripts/proxy-test.sh` | Smoke-test a running proxy |
239+
| `scripts/docker-build.sh` | Build the Docker image |
240+
| `scripts/install-python.sh` | Install Python package (dev or prod) |
241+
| `scripts/install-typescript.sh` | Install TypeScript package |
242+
| `scripts/test-all.sh` | Run full test suite (Python + TypeScript) |
243+
214244
## License
215245

216246
[MIT](LICENSE)

docs/CoverageMatrix.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title: "Test Coverage Matrix"
55

66
# Test Coverage Matrix
77

8-
Correlates documented features with test coverage. The project includes 775 Python tests across 14 test files and 466 TypeScript tests across 13 test files, for a total of 1,241 tests.
8+
Correlates documented features with test coverage. The project includes 855 Python tests across 15 test files and 511 TypeScript tests across 13 test files, for a total of 1,366 tests.
99

1010
---
1111

@@ -26,7 +26,8 @@ Correlates documented features with test coverage. The project includes 775 Pyth
2626
| New Connectors (local providers, browser) | 93 | `test_new_connectors.py` | Covered |
2727
| CDK Specialized + Mixins + Helpers | 97 | `test_specialized.py` | Covered |
2828
| Local Provider Connectors | 28 | `test_providers.py` | Covered |
29-
| **Total** | **775** | **14 files** | |
29+
| Proxy Server + Docker Infrastructure | 80 | `test_docker.py` | Covered |
30+
| **Total** | **855** | **15 files** | |
3031

3132
### TypeScript Test Suite
3233

@@ -39,12 +40,12 @@ Correlates documented features with test coverage. The project includes 775 Pyth
3940
| CapabilityPool | 15 | `pool.test.ts` | Covered |
4041
| ModelMesh facade | 16 | `mesh.test.ts` | Covered |
4142
| Router | 5 | `router.test.ts` | Covered |
42-
| Pre-shipped Connectors + Cloud/Local Providers + RuntimeEnvironment + Registry + Runtime Guard | 185 | `connectors.test.ts` | Covered |
43+
| Pre-shipped Connectors + Cloud/Local Providers + RuntimeEnvironment + Registry + Runtime Guard + Docker Infrastructure | 230 | `connectors.test.ts` | Covered |
4344
| MeshConfig + Auto-detect + LOCAL_PROVIDER_REGISTRY | 30 | `config.test.ts` | Covered |
4445
| MeshClient (OpenAI compat) | 16 | `client.test.ts` | Covered |
4546
| Secret Stores (env, dotenv, json, memory, encrypted, keyring) | 55 | `secret-stores.test.ts` | Covered |
4647
| CORS Proxy | 12 | `proxy.test.ts` | Covered |
47-
| **Total** | **466** | **13 files** | |
48+
| **Total** | **511** | **13 files** | |
4849

4950
---
5051

@@ -235,6 +236,28 @@ Correlates documented features with test coverage. The project includes 775 Pyth
235236
| HttpHealthDiscovery | `BaseClasses.md` | -- | Requires HTTP mock; coverage gap |
236237
| CDK test helpers | `Helpers.md` | `TestConnectorTestHarness`, `TestMockHttpClient` (in `test_specialized.py`) | mockCompletionRequest, mockModelSnapshot, MockHttpClient, ConnectorTestHarness |
237238

239+
### 15. Proxy Server & Docker Deployment (`docs/guides/ProxyGuide.md`)
240+
241+
| Feature | Doc Reference | Test(s) | Notes |
242+
| --- | --- | --- | --- |
243+
| Dockerfile structure | `ProxyGuide.md` | `TestDockerfile` (10 tests) | Base image, COPY, pip install, pyyaml, EXPOSE, ENTRYPOINT |
244+
| docker-compose.yaml | `ProxyGuide.md` | `TestDockerCompose` (7 tests) | Service, port mapping, env_file, config mount |
245+
| modelmesh.yaml config | `ProxyGuide.md` | `TestModelMeshConfig` (10 tests) | Sections, secret refs, no hardcoded keys |
246+
| .env.example template | `ProxyGuide.md` | `TestEnvExample` (5 tests) | Key presence, empty values |
247+
| .gitignore protects secrets | -- | `TestGitignore` (2 tests) | .env and .env.* ignored |
248+
| Automation scripts | `ProxyGuide.md` | `TestScripts` (15 tests) | Existence, content, shebang, strict mode |
249+
| Browser test page | `ProxyGuide.md` | `TestBrowserTestPage` (10 tests) | HTML validity, no deps, fetch API, streaming, SSE |
250+
| Proxy module structure | -- | `TestProxyModuleStructure` (7 tests) | Package, __init__, __main__, server, cli |
251+
| Proxy CLI argument parsing | -- | `TestProxyCLI` (2 tests) | Default and custom args |
252+
| Live proxy HTTP integration | `ProxyGuide.md` | `TestProxyLiveHTTP` (10 tests) | Health, models, chat, streaming, CORS, 400, 404, usage, status tracking |
253+
| ServerStatus dataclass | -- | `TestServerStatus` (3 tests in `test_proxy.py`) | Defaults, custom values, asdict |
254+
| ProxyState status reporting | -- | `TestProxyState` (3 tests in `test_proxy.py`) | Not running, running, counters |
255+
| Bearer token auth | -- | `TestAuthTokenValidation` (4 tests in `test_proxy.py`) | No token, token configured, server stores token |
256+
| /v1/models response shape | -- | `TestModelsEndpoint` (2 tests in `test_proxy.py`) | Pool IDs as models, OpenAI list format |
257+
| Request parsing | -- | `TestRequestParsing` (4 tests in `test_proxy.py`) | Chat completion, streaming, tools, defaults |
258+
| Response serialization | -- | `TestCompletionResponseSerialization` (5 tests in `test_proxy.py`) | Basic, streaming chunk, UUID gen, JSON serializable, tool calls |
259+
| ProxyServer initialization | -- | `TestProxyServerInit` (5 tests in `test_proxy.py`) | MeshConfig, dict, invalid type, status, mesh property |
260+
238261
---
239262

240263
## Coverage Gaps
@@ -277,3 +300,4 @@ Correlates documented features with test coverage. The project includes 775 Pyth
277300
| `interfaces/Discovery.md` | Discovery ABC | *(no dedicated tests)* | Gap |
278301
| `guides/BrowserUsage.md` | BrowserBaseProvider, CORS proxy, createBrowser() | Browser provider tests | Direct |
279302
| `ConnectorInterfaces.md` (Audio) | AudioRequest, AudioResponse, audio namespace | Audio interface tests | Direct |
303+
| `guides/ProxyGuide.md` | Proxy server, Docker, CLI, REST API, browser access | `test_docker.py` (80 tests) + `test_proxy.py` (26 tests) + `connectors.test.ts` Docker section | Direct |

0 commit comments

Comments
 (0)