Skip to content

Commit 3c96a23

Browse files
haasonsaasclaude
andcommitted
Transform Mocktopus into proper LLM API mock server
Major refactoring to align with product vision: - Add HTTP server that mimics OpenAI/Anthropic APIs - Implement proper SSE streaming support - Support function/tool calling - Add server modes: mock, record (TODO), replay (TODO) - Create comprehensive CLI with serve, validate, example commands - Add multiple example scenarios for different use cases - Update documentation to reflect actual functionality - Add aiohttp dependency for server implementation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent de26150 commit 3c96a23

File tree

8 files changed

+1137
-100
lines changed

8 files changed

+1137
-100
lines changed

README.md

Lines changed: 239 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,285 @@
1-
# Mocktopus 🐙
1+
# 🐙 Mocktopus
22

3-
**Multi‑armed mocks for LLM apps.** Deterministic, dataset‑driven mocks for OpenAI‑style chat
4-
completions (plus tool calls & streaming simulation). Designed for evals, CI, and reproducible tests.
3+
> Multi-armed mocks for LLM apps
54
6-
## Why
7-
- Make flaky LLM tests deterministic.
8-
- Run evals offline and in CI without credentials.
9-
- Record once, replay many times (golden fixtures).
10-
- Simulate streaming and tool calls without hitting real APIs.
5+
**Mocktopus** is a drop-in replacement for OpenAI/Anthropic APIs, designed to make your LLM application tests fast, deterministic, and cost-free.
116

12-
## Features (MVP)
13-
- 🧪 `Scenario` that loads rules from YAML and returns canned LLM responses.
14-
- 🧵 Streaming simulation compatible with `stream=True` style iteration.
15-
- 🧰 Tool call stubs (`assistant.tool_calls`) with optional structured args.
16-
- 🧩 Two ways to use:
17-
1. **Dependency‑injected client**: `OpenAIStubClient(scenario)`
18-
2. **Monkey‑patch** (best‑effort): `with patch_openai(scenario): ...`
7+
[![CI](https://github.com/evalops/mocktopus/actions/workflows/ci.yml/badge.svg)](https://github.com/evalops/mocktopus/actions)
8+
[![PyPI](https://img.shields.io/pypi/v/mocktopus)](https://pypi.org/project/mocktopus/)
9+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
1910

20-
> Patching SDK internals can be brittle across versions. Prefer dependency injection for reliability.
11+
## Why Mocktopus?
2112

22-
Roadmap: HTTP fixtures (VCR‑like), vector/RAG stubs, Anthropic adapter, record mode.
13+
Testing LLM applications is challenging:
14+
- **Non-deterministic**: Same prompt, different responses
15+
- **Expensive**: Every test run costs API credits
16+
- **Slow**: API calls add latency to test suites
17+
- **Network-dependent**: Can't run tests offline
18+
- **Complex workflows**: Tool calls and streaming complicate testing
19+
20+
Mocktopus solves these problems by providing a local mock server that perfectly mimics LLM APIs.
21+
22+
## Features
23+
24+
**Drop-in replacement** - Just change your base URL
25+
**Deterministic responses** - Same input → same output
26+
**Tool/function calling** - Full support for complex workflows
27+
**Streaming** - Server-sent events (SSE) support
28+
**Multiple providers** - OpenAI and Anthropic compatible
29+
**Zero cost** - No API charges for tests
30+
**Fast** - No network latency
31+
**Offline** - Run tests without internet
32+
33+
## Installation
2334

24-
## Install
2535
```bash
26-
pip install -e ".[dev]" # when cloned locally
27-
# or just install from source once published:
28-
# pip install mocktopus
36+
pip install mocktopus
2937
```
3038

31-
## Quick start
39+
## Quick Start
3240

33-
1) Define a fixture:
41+
### 1. Create a scenario file (`scenario.yaml`):
3442

3543
```yaml
36-
# examples/haiku.yaml
3744
version: 1
3845
rules:
3946
- type: llm.openai
4047
when:
41-
messages_contains: "haiku"
42-
model: "*"
48+
model: "gpt-4*"
49+
messages_contains: "hello"
4350
respond:
44-
content: "Silent bay at dusk\nEight arms fold into the deep\nTides keep time for stars."
45-
usage:
46-
input_tokens: 12
47-
output_tokens: 17
48-
stream: false
51+
content: "Hello! How can I help you today?"
52+
```
53+
54+
### 2. Start the mock server:
55+
56+
```bash
57+
mocktopus serve -s scenario.yaml
4958
```
5059

51-
2) Use the dependency‑injected stub client:
60+
### 3. Point your app to Mocktopus:
5261

5362
```python
54-
from mocktopus import Scenario, OpenAIStubClient, load_yaml
63+
from openai import OpenAI
5564

56-
scenario = load_yaml("examples/haiku.yaml")
57-
client = OpenAIStubClient(scenario)
65+
# Instead of the real API:
66+
# client = OpenAI(api_key="sk-...")
5867

59-
resp = client.chat.completions.create(
60-
model="gpt-4o-mini",
61-
messages=[{"role": "user", "content": "Write a haiku about an octopus"}],
68+
# Use Mocktopus:
69+
client = OpenAI(
70+
base_url="http://localhost:8080/v1",
71+
api_key="mock-key" # Any string works
6272
)
6373

64-
print(resp.choices[0].message.content)
65-
# -> Silent bay at dusk ...
74+
response = client.chat.completions.create(
75+
model="gpt-4",
76+
messages=[{"role": "user", "content": "hello"}]
77+
)
78+
print(response.choices[0].message.content)
79+
# Output: "Hello! How can I help you today?"
6680
```
6781

68-
3) Or (beta) patch the OpenAI SDK dynamically:
82+
## Usage Modes
6983

70-
```python
71-
from mocktopus import load_yaml, patch_openai
72-
from openai import OpenAI
84+
### Mock Mode (Default)
85+
Use predefined YAML scenarios for deterministic responses:
7386

74-
scenario = load_yaml("examples/haiku.yaml")
75-
with patch_openai(scenario):
76-
client = OpenAI()
77-
resp = client.chat.completions.create(
78-
model="gpt-4o-mini",
79-
messages=[{"role": "user", "content": "Write a haiku about an octopus"}],
80-
)
81-
print(resp.choices[0].message.content)
87+
```bash
88+
mocktopus serve -s examples/chat-basic.yaml
8289
```
8390

84-
4) Streaming simulation:
91+
### Record Mode (Coming Soon)
92+
Proxy and record real API calls for later replay:
8593

86-
```python
87-
resp = client.chat.completions.create(
88-
model="gpt-4o-mini",
89-
messages=[{"role": "user", "content": "haiku please"}],
90-
stream=True,
91-
)
92-
for event in resp:
93-
delta = event.choices[0].delta.content or ""
94-
print(delta, end="")
94+
```bash
95+
mocktopus serve --mode record --recordings-dir ./recordings
9596
```
9697

97-
## Pytest usage
98+
### Replay Mode (Coming Soon)
99+
Replay previously recorded API interactions:
98100

99-
Add to your test conftest:
100-
```python
101-
pytest_plugins = ["mocktopus.pytest_plugin"]
101+
```bash
102+
mocktopus serve --mode replay --recordings-dir ./recordings
102103
```
103104

104-
Example test:
105+
## Scenario Examples
106+
107+
### Basic Chat Response
108+
109+
```yaml
110+
version: 1
111+
rules:
112+
- type: llm.openai
113+
when:
114+
messages_contains: "weather"
115+
respond:
116+
content: "It's sunny today!"
117+
```
118+
119+
### Function Calling
120+
121+
```yaml
122+
version: 1
123+
rules:
124+
- type: llm.openai
125+
when:
126+
messages_contains: "weather"
127+
respond:
128+
tool_calls:
129+
- id: "call_123"
130+
type: "function"
131+
function:
132+
name: "get_weather"
133+
arguments: '{"location": "San Francisco"}'
134+
```
135+
136+
### Streaming Response
137+
138+
```yaml
139+
version: 1
140+
rules:
141+
- type: llm.openai
142+
when:
143+
model: "*"
144+
respond:
145+
content: "This will be streamed..."
146+
delay_ms: 50 # Delay between chunks
147+
chunk_size: 5 # Characters per chunk
148+
```
149+
150+
### Limited Usage
151+
152+
```yaml
153+
version: 1
154+
rules:
155+
- type: llm.openai
156+
when:
157+
messages_contains: "test"
158+
times: 3 # Only responds 3 times
159+
respond:
160+
content: "Limited response"
161+
```
162+
163+
## CLI Commands
164+
165+
### Start Server
166+
```bash
167+
# Basic usage
168+
mocktopus serve -s scenario.yaml
169+
170+
# Custom port
171+
mocktopus serve -s scenario.yaml -p 9000
172+
173+
# Verbose logging
174+
mocktopus serve -s scenario.yaml -v
175+
```
176+
177+
### Test Scenarios
178+
```bash
179+
# Validate a scenario file
180+
mocktopus validate scenario.yaml
181+
182+
# Simulate a request without starting server
183+
mocktopus simulate -s scenario.yaml --prompt "Hello"
184+
185+
# Generate example scenarios
186+
mocktopus example --type basic > my-scenario.yaml
187+
mocktopus example --type tools > tools-scenario.yaml
188+
```
189+
190+
## Testing with Mocktopus
191+
192+
### Pytest Integration
193+
105194
```python
106-
def test_haiku(use_mocktopus):
107-
use_mocktopus.load_yaml("examples/haiku.yaml")
108-
client = use_mocktopus.openai_client() # OpenAIStubClient bound to the scenario
109-
out = client.chat.completions.create(
110-
model="gpt-4o-mini",
111-
messages=[{"role": "user", "content": "haiku"}],
195+
import pytest
196+
from mocktopus import use_mocktopus
197+
198+
def test_my_llm_app(use_mocktopus):
199+
# Load scenario
200+
use_mocktopus.load_yaml("tests/scenarios/test.yaml")
201+
202+
# Get a client
203+
client = use_mocktopus.openai_client()
204+
205+
# Test your app
206+
response = client.chat.completions.create(
207+
model="gpt-4",
208+
messages=[{"role": "user", "content": "test"}]
112209
)
113-
assert "Silent bay" in out.choices[0].message.content
210+
assert "expected" in response.choices[0].message.content
114211
```
115212

116-
## CLI
213+
### Continuous Integration
117214

118-
```bash
119-
mocktopus simulate --fixture examples/haiku.yaml --prompt "haiku about octopus"
215+
```yaml
216+
# .github/workflows/test.yml
217+
name: Tests
218+
on: [push, pull_request]
219+
220+
jobs:
221+
test:
222+
runs-on: ubuntu-latest
223+
steps:
224+
- uses: actions/checkout@v4
225+
- uses: actions/setup-python@v5
226+
- run: pip install -e .
227+
- run: mocktopus serve -s tests/scenarios.yaml &
228+
- run: pytest # Your tests hit localhost:8080
120229
```
121230
122-
## Caveats
123-
- The OpenAI SDK patcher targets modern `openai` Python SDKs and may break across versions. Prefer the stub client where possible.
124-
- YAML matching is currently simple (substring + model glob). Extend as needed.
231+
## Advanced Features
232+
233+
### Pattern Matching
234+
235+
Mocktopus supports multiple matching strategies:
236+
237+
- **Exact match**: `messages_contains: "exact phrase"`
238+
- **Regex**: `messages_regex: "\\d+ items?"`
239+
- **Glob**: `model: "gpt-4*"`
240+
241+
### Response Configuration
242+
243+
```yaml
244+
respond:
245+
content: "Response text"
246+
delay_ms: 100 # Simulate latency
247+
usage:
248+
input_tokens: 10
249+
output_tokens: 20
250+
# For streaming
251+
chunk_size: 10 # Characters per chunk
252+
```
253+
254+
## Roadmap
255+
256+
- [x] OpenAI chat completions API
257+
- [x] Streaming support (SSE)
258+
- [x] Function/tool calling
259+
- [x] Anthropic messages API
260+
- [ ] Recording & replay
261+
- [ ] Embeddings API
262+
- [ ] Assistants API
263+
- [ ] Image generation
264+
- [ ] Semantic similarity matching
265+
- [ ] Response templating
266+
- [ ] Load testing mode
267+
268+
## Contributing
269+
270+
We welcome contributions! See our [Contributing Guide](CONTRIBUTING.md) for details.
125271

126272
## License
127-
MIT
273+
274+
MIT - See [LICENSE](LICENSE) for details.
275+
276+
## Links
277+
278+
- [GitHub Repository](https://github.com/evalops/mocktopus)
279+
- [PyPI Package](https://pypi.org/project/mocktopus/)
280+
- [Documentation](https://github.com/evalops/mocktopus/wiki)
281+
- [Issue Tracker](https://github.com/evalops/mocktopus/issues)
282+
283+
---
284+
285+
Made with 🐙 by [EvalOps](https://github.com/evalops)

0 commit comments

Comments
 (0)