mockstack 0.7.3__tar.gz → 0.9.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.
- {mockstack-0.7.3 → mockstack-0.9.0}/Dockerfile +1 -1
- {mockstack-0.7.3 → mockstack-0.9.0}/PKG-INFO +24 -15
- {mockstack-0.7.3 → mockstack-0.9.0}/README.md +19 -12
- {mockstack-0.7.3 → mockstack-0.9.0}/SECURITY.md +1 -1
- mockstack-0.9.0/docs/ollama.md +87 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/mockstack-langchain-example.ipynb +73 -8
- mockstack-0.9.0/examples/llm/templates/ollama-openai-v1-chat-completions.j2 +35 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mkdocs.yml +12 -3
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/config.py +2 -2
- mockstack-0.9.0/mockstack/exceptions.py +6 -0
- mockstack-0.9.0/mockstack/llm/ollama.py +50 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/filefixtures.py +22 -16
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/proxyrules.py +12 -5
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/templating.py +44 -1
- mockstack-0.9.0/mockstack/tests/llm/__init__.py +0 -0
- mockstack-0.9.0/mockstack/tests/llm/test_ollama.py +95 -0
- mockstack-0.9.0/mockstack/tests/routers/__init__.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/strategies/test_filefixtures.py +7 -26
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_templating.py +29 -6
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/PKG-INFO +24 -15
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/SOURCES.txt +7 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/requires.txt +3 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/pyproject.toml +7 -2
- {mockstack-0.7.3 → mockstack-0.9.0}/uv.lock +20 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.dockerignore +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.env.example +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/ci.yml +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/publish-docs.yml +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/publish-to-dockerhub.yml +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.gitignore +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/.pre-commit-config.yaml +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/CODE_OF_CONDUCT.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/LICENSE +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docker-entrypoint.sh +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docs/assets/favicon.ico +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docs/assets/logo.png +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docs/assets/mockstack.png +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docs/configuration.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docs/strategies/base.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docs/strategies/filefixtures.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/docs/strategies/proxyrules.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/.env.example +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/README.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/templates/servicename-api-v1-items.j2 +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/filefixtures-with-templates/templates/servicename2-api-v2-user.533ec889-7c68-45c8-b21e-4a7e455d1234.j2 +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/README.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/templates/openai-v1-chat-completions.j2 +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/llm/templates/openai-v1-deployments-gpt-4o-chat-completions.j2 +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/proxyrules-with-rules-file/.env.example +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/proxyrules-with-rules-file/README.md +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/examples/proxyrules-with-rules-file/rules.yml +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/__init__.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/constants.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/display.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/identifiers.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/intent.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/lifespan.py +0 -0
- {mockstack-0.7.3/mockstack/routers → mockstack-0.9.0/mockstack/llm}/__init__.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/main.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/middleware.py +0 -0
- {mockstack-0.7.3/mockstack/tests → mockstack-0.9.0/mockstack/routers}/__init__.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/routers/catchall.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/routers/homepage.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/__init__.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/base.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/create_mixin.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/strategies/factory.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/telemetry.py +0 -0
- {mockstack-0.7.3/mockstack/tests/fixtures/templates → mockstack-0.9.0/mockstack/tests}/__init__.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/conftest.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/fixtures/proxyrules.yml +0 -0
- {mockstack-0.7.3/mockstack/tests/routers → mockstack-0.9.0/mockstack/tests/fixtures/templates}/__init__.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/fixtures/templates/example-template.j2 +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/routers/test_catchall.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/routers/test_homepage.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/strategies/test_create_mixin.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/strategies/test_proxyrules.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_display.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_identifiers.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_middleware.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack/tests/test_telemetry.py +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/dependency_links.txt +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/entry_points.txt +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/mockstack.egg-info/top_level.txt +0 -0
- {mockstack-0.7.3 → mockstack-0.9.0}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mockstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: An API mocking workhorse
|
|
5
5
|
Author-email: Adam Ever-Hadani <mockstack.contact@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Issues, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/promptromp/mockstack
|
|
8
|
+
Project-URL: Issues, https://github.com/promptromp/mockstack/issues
|
|
9
9
|
Keywords: mocking,integration-testing,testing,microservices,api
|
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Programming Language :: Python
|
|
@@ -27,15 +27,17 @@ Requires-Dist: pyyaml>=6.0.2
|
|
|
27
27
|
Requires-Dist: rich>=14.0.0
|
|
28
28
|
Requires-Dist: typer>=0.15.2
|
|
29
29
|
Requires-Dist: uvicorn>=0.34.2
|
|
30
|
+
Provides-Extra: llm
|
|
31
|
+
Requires-Dist: ollama>=0.4.8; extra == "llm"
|
|
30
32
|
Dynamic: license-file
|
|
31
33
|
|
|
32
|
-

|
|
33
35
|
|
|
34
36
|
--------------------------------------------------------------------------------
|
|
35
37
|
|
|
36
38
|
|
|
37
|
-
[](https://github.com/promptromp/mockstack/actions/workflows/ci.yml)
|
|
40
|
+
[](https://github.com/promptromp/mockstack/blob/main/LICENSE)
|
|
39
41
|
[](https://pypi.org/project/mockstack/)
|
|
40
42
|
[](https://pypi.org/project/mockstack/)
|
|
41
43
|
|
|
@@ -50,7 +52,7 @@ Use mockstack for:
|
|
|
50
52
|
|
|
51
53
|
* **Integration Testing** :ok_hand:. Creating a consistent environment for running integration tests on a single component, using fixture responses.
|
|
52
54
|
|
|
53
|
-
* **LLM-powered Workflows** :alien:. Speeding up development of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs, you
|
|
55
|
+
* **LLM-powered Workflows** :alien:. Speeding up development and reducing per-token costs of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs. Optional [Ollama](https://ollama.com/) integration allows for realistic mocking of 3rd party LLMs without changing a line of code in your project. In addition, when you want to have a consistent response from a tool while you're tuning prompts or debugging other aspects of a particular trace you can create various fixture responses with varying levels of dynamic content that is template-driven. **mockstack** can give you a solid foundation for this.
|
|
54
56
|
|
|
55
57
|
* **Chaos Engineering** :boom:. mockstack can simulate various real-world runtime error scenarios such as timeouts, http error codes, and invalid response payloads. This can be a great way to do some upfront [Chaos Monkey](https://github.com/Netflix/chaosmonkey) type of testing on software components.
|
|
56
58
|
|
|
@@ -75,21 +77,28 @@ or install into a persistent environment and add it to the PATH with:
|
|
|
75
77
|
|
|
76
78
|
## Usage
|
|
77
79
|
|
|
78
|
-
See the [examples](https://github.com/
|
|
80
|
+
See the [examples](https://github.com/promptromp/mockstack/blob/main/examples/) directory for complete examples with documentation.
|
|
79
81
|
|
|
80
|
-
Available configuration options are [here](https://github.com/
|
|
82
|
+
Available configuration options are [here](https://github.com/promptromp/mockstack/blob/main/mockstack/config.py).
|
|
81
83
|
|
|
82
|
-
Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments.
|
|
84
|
+
Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
Minimal example to get you started:
|
|
83
88
|
|
|
84
89
|
```shell
|
|
85
|
-
|
|
90
|
+
mkdir -p ~/mockstack-templates
|
|
91
|
+
echo '{"message": "Hello from mockstack!"}' > ~/mockstack-templates/myservice-api-myresource.j2
|
|
92
|
+
|
|
86
93
|
export MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
|
|
87
|
-
export MOCKSTACK__OPENTELEMETRY__ENABLED=true
|
|
88
|
-
export MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
|
|
89
94
|
uvx mockstack
|
|
90
95
|
```
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
You can then hit `http://localhost:8000/myservice/api/myresource/23faa8cf-5daa-4bcb-8c92-27018b712aa9` (or any other UUID).
|
|
98
|
+
|
|
99
|
+
This is of course just the tip of the iceberg.
|
|
100
|
+
|
|
101
|
+
See also the included [.env.example](https://github.com/promptromp/mockstack/blob/main/.env.example) for more settings you are likely to find useful. You can copy that file to `.env` and fill in configuration as needed based on the given examples.
|
|
93
102
|
|
|
94
103
|
Out of the box, you get the following behavior when using the default `filefixtures` strategy:
|
|
95
104
|
|
|
@@ -118,7 +127,7 @@ Linting, formatting, static type checks etc. are all managed via [pre-commit](ht
|
|
|
118
127
|
|
|
119
128
|
If you are contributing to development, you will want to clone this project, and can then install it locally with:
|
|
120
129
|
|
|
121
|
-
gh repo clone
|
|
130
|
+
gh repo clone promptromp/mockstack
|
|
122
131
|
cd mockstack/
|
|
123
132
|
uv sync
|
|
124
133
|
uv pip install -e .
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
--------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
[](https://github.com/promptromp/mockstack/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/promptromp/mockstack/blob/main/LICENSE)
|
|
8
8
|
[](https://pypi.org/project/mockstack/)
|
|
9
9
|
[](https://pypi.org/project/mockstack/)
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ Use mockstack for:
|
|
|
19
19
|
|
|
20
20
|
* **Integration Testing** :ok_hand:. Creating a consistent environment for running integration tests on a single component, using fixture responses.
|
|
21
21
|
|
|
22
|
-
* **LLM-powered Workflows** :alien:. Speeding up development of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs, you
|
|
22
|
+
* **LLM-powered Workflows** :alien:. Speeding up development and reducing per-token costs of LLM-based workflows and tools for use with frameworks such as [LangChain](https://python.langchain.com/docs/introduction/), [LangGraph](https://www.langchain.com/langgraph) and others. When developing LLM-driven execution graphs. Optional [Ollama](https://ollama.com/) integration allows for realistic mocking of 3rd party LLMs without changing a line of code in your project. In addition, when you want to have a consistent response from a tool while you're tuning prompts or debugging other aspects of a particular trace you can create various fixture responses with varying levels of dynamic content that is template-driven. **mockstack** can give you a solid foundation for this.
|
|
23
23
|
|
|
24
24
|
* **Chaos Engineering** :boom:. mockstack can simulate various real-world runtime error scenarios such as timeouts, http error codes, and invalid response payloads. This can be a great way to do some upfront [Chaos Monkey](https://github.com/Netflix/chaosmonkey) type of testing on software components.
|
|
25
25
|
|
|
@@ -44,21 +44,28 @@ or install into a persistent environment and add it to the PATH with:
|
|
|
44
44
|
|
|
45
45
|
## Usage
|
|
46
46
|
|
|
47
|
-
See the [examples](https://github.com/
|
|
47
|
+
See the [examples](https://github.com/promptromp/mockstack/blob/main/examples/) directory for complete examples with documentation.
|
|
48
48
|
|
|
49
|
-
Available configuration options are [here](https://github.com/
|
|
49
|
+
Available configuration options are [here](https://github.com/promptromp/mockstack/blob/main/mockstack/config.py).
|
|
50
50
|
|
|
51
|
-
Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments.
|
|
51
|
+
Setting individual options can be done either through an `.env` file, individual environment variables, or command-line arguments.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Minimal example to get you started:
|
|
52
55
|
|
|
53
56
|
```shell
|
|
54
|
-
|
|
57
|
+
mkdir -p ~/mockstack-templates
|
|
58
|
+
echo '{"message": "Hello from mockstack!"}' > ~/mockstack-templates/myservice-api-myresource.j2
|
|
59
|
+
|
|
55
60
|
export MOCKSTACK__TEMPLATES_DIR=~/mockstack-templates/
|
|
56
|
-
export MOCKSTACK__OPENTELEMETRY__ENABLED=true
|
|
57
|
-
export MOCKSTACK__OPENTELEMETRY__CAPTURE_RESPONSE_BODY=true
|
|
58
61
|
uvx mockstack
|
|
59
62
|
```
|
|
60
63
|
|
|
61
|
-
|
|
64
|
+
You can then hit `http://localhost:8000/myservice/api/myresource/23faa8cf-5daa-4bcb-8c92-27018b712aa9` (or any other UUID).
|
|
65
|
+
|
|
66
|
+
This is of course just the tip of the iceberg.
|
|
67
|
+
|
|
68
|
+
See also the included [.env.example](https://github.com/promptromp/mockstack/blob/main/.env.example) for more settings you are likely to find useful. You can copy that file to `.env` and fill in configuration as needed based on the given examples.
|
|
62
69
|
|
|
63
70
|
Out of the box, you get the following behavior when using the default `filefixtures` strategy:
|
|
64
71
|
|
|
@@ -87,7 +94,7 @@ Linting, formatting, static type checks etc. are all managed via [pre-commit](ht
|
|
|
87
94
|
|
|
88
95
|
If you are contributing to development, you will want to clone this project, and can then install it locally with:
|
|
89
96
|
|
|
90
|
-
gh repo clone
|
|
97
|
+
gh repo clone promptromp/mockstack
|
|
91
98
|
cd mockstack/
|
|
92
99
|
uv sync
|
|
93
100
|
uv pip install -e .
|
|
@@ -11,4 +11,4 @@ We do not currently run any bug bounty programs.
|
|
|
11
11
|
## Vulnerability disclosures
|
|
12
12
|
|
|
13
13
|
Critical vulnerabilities will be disclosed via GitHub's
|
|
14
|
-
[security advisory](https://github.com/
|
|
14
|
+
[security advisory](https://github.com/promptromp/mockstack/security) system.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Ollama Integration
|
|
2
|
+
|
|
3
|
+
Mockstack provides integration with [Ollama](https://ollama.com/), allowing you to use real LLM responses in your mock templates. This is particularly useful for development, debugging, and integration testing scenarios where you want to capture the non-deterministic nature of LLM responses.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
To use the Ollama integration, you'll need:
|
|
8
|
+
|
|
9
|
+
1. Mockstack installed with the optional `llm` dependencies:
|
|
10
|
+
```bash
|
|
11
|
+
uv pip install mockstack[llm]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. [Ollama](https://ollama.com/) installed locally with at least one model (e.g., "llama3.2")
|
|
15
|
+
|
|
16
|
+
## Basic Usage
|
|
17
|
+
|
|
18
|
+
The Ollama integration works by routing requests to a template file that uses the special `ollama` template function.
|
|
19
|
+
|
|
20
|
+
1. Configure your LLM client to hit an endpoint that maps to a template filepath calling the `ollama` method, e.g.:
|
|
21
|
+
```python
|
|
22
|
+
from langchain_openai import ChatOpenAI
|
|
23
|
+
|
|
24
|
+
llm = ChatOpenAI(
|
|
25
|
+
model="gpt-4o",
|
|
26
|
+
base_url="http://localhost:8000/ollama/openai/v1",
|
|
27
|
+
api_key="SOME_STRING_THAT_DOES_NOT_MATTER",
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
2. Make requests as you normally would:
|
|
32
|
+
```python
|
|
33
|
+
messages = [
|
|
34
|
+
(
|
|
35
|
+
"system",
|
|
36
|
+
"You are a helpful assistant that translates English to French. Translate the user sentence.",
|
|
37
|
+
),
|
|
38
|
+
("human", "mockstack is pretty cool. But LLMs are way cooler"),
|
|
39
|
+
]
|
|
40
|
+
ai_msg = llm.invoke(messages)
|
|
41
|
+
print(ai_msg.content)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Integration with Templates
|
|
45
|
+
|
|
46
|
+
You can use Ollama responses within your Jinja templates via the provided `ollama` template function. This allows you to:
|
|
47
|
+
|
|
48
|
+
1. Mix static and dynamic content
|
|
49
|
+
2. Apply transformations to the LLM responses
|
|
50
|
+
3. Create conditional logic based on the responses
|
|
51
|
+
|
|
52
|
+
Example template structure:
|
|
53
|
+
|
|
54
|
+
```jinja
|
|
55
|
+
{
|
|
56
|
+
"id": "chatcmpl-123",
|
|
57
|
+
"object": "chat.completion",
|
|
58
|
+
"created": 1677652288,
|
|
59
|
+
"model": "gpt-4.1",
|
|
60
|
+
"choices": [{
|
|
61
|
+
"index": 0,
|
|
62
|
+
"message": {
|
|
63
|
+
"role": "assistant",
|
|
64
|
+
"content": "{{ ollama(request_json.messages, 'llama3.2') | json_escape }}"
|
|
65
|
+
},
|
|
66
|
+
"finish_reason": "stop"
|
|
67
|
+
}],
|
|
68
|
+
"usage": {
|
|
69
|
+
"prompt_tokens": 9,
|
|
70
|
+
"completion_tokens": 12,
|
|
71
|
+
"total_tokens": 21
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Best Practices
|
|
77
|
+
|
|
78
|
+
1. **Caching**: Consider implementing caching for frequently used responses to improve performance
|
|
79
|
+
2. **Model Selection**: Choose the appropriate model based on your testing needs
|
|
80
|
+
3. **Error Handling**: Implement proper error handling in your templates
|
|
81
|
+
4. **Performance**: Be mindful of response times when using real LLM responses
|
|
82
|
+
|
|
83
|
+
## Limitations
|
|
84
|
+
|
|
85
|
+
- The integration requires a local Ollama instance
|
|
86
|
+
- Response times will be slower than static templates
|
|
87
|
+
- Model availability depends on your local setup
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"cell_type": "code",
|
|
14
|
-
"execution_count":
|
|
14
|
+
"execution_count": 7,
|
|
15
15
|
"metadata": {},
|
|
16
16
|
"outputs": [],
|
|
17
17
|
"source": [
|
|
@@ -22,11 +22,31 @@
|
|
|
22
22
|
"#!uv pip install langchain langchain-openai"
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
"cell_type": "code",
|
|
27
|
+
"execution_count": null,
|
|
28
|
+
"metadata": {},
|
|
29
|
+
"outputs": [],
|
|
30
|
+
"source": [
|
|
31
|
+
"from ollama import chat\n",
|
|
32
|
+
"from ollama import ChatResponse\n",
|
|
33
|
+
"\n",
|
|
34
|
+
"response: ChatResponse = chat(model='llama3.2', messages=[\n",
|
|
35
|
+
" {\n",
|
|
36
|
+
" 'role': 'user',\n",
|
|
37
|
+
" 'content': 'Why is the sky blue?',\n",
|
|
38
|
+
" },\n",
|
|
39
|
+
"])\n",
|
|
40
|
+
"print(response['message']['content'])\n",
|
|
41
|
+
"# or access fields directly from the response object\n",
|
|
42
|
+
"print(response.message.content)"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
25
45
|
{
|
|
26
46
|
"cell_type": "markdown",
|
|
27
47
|
"metadata": {},
|
|
28
48
|
"source": [
|
|
29
|
-
"## Example #1:
|
|
49
|
+
"## Example #1: Static template-based mocking (`filefixtures` strategy)\n",
|
|
30
50
|
"\n",
|
|
31
51
|
"Here we simply use the **filefixtures** strategy to route requesets coming in for a certain URL to a template file with the appropriate name.\n",
|
|
32
52
|
"\n",
|
|
@@ -42,7 +62,7 @@
|
|
|
42
62
|
},
|
|
43
63
|
{
|
|
44
64
|
"cell_type": "code",
|
|
45
|
-
"execution_count":
|
|
65
|
+
"execution_count": 2,
|
|
46
66
|
"metadata": {},
|
|
47
67
|
"outputs": [],
|
|
48
68
|
"source": [
|
|
@@ -86,7 +106,7 @@
|
|
|
86
106
|
},
|
|
87
107
|
{
|
|
88
108
|
"cell_type": "code",
|
|
89
|
-
"execution_count":
|
|
109
|
+
"execution_count": 3,
|
|
90
110
|
"metadata": {},
|
|
91
111
|
"outputs": [],
|
|
92
112
|
"source": [
|
|
@@ -120,17 +140,62 @@
|
|
|
120
140
|
"cell_type": "markdown",
|
|
121
141
|
"metadata": {},
|
|
122
142
|
"source": [
|
|
123
|
-
"## Example #2:
|
|
143
|
+
"## Example #2: Dynamic template-based mocking with ollama integration\n",
|
|
124
144
|
"\n",
|
|
125
|
-
"
|
|
145
|
+
"In this example we use the [ollama-python](https://github.com/ollama/ollama-python) integration to return mock responses that actually come from a real LLM running on your host! This is a great way to go a step further in development, debugging, and integration testing scenarios where LLMs and their non-determinism are important to capture.\n",
|
|
146
|
+
"\n",
|
|
147
|
+
"For this example you will need to make sure you have the following:\n",
|
|
148
|
+
"\n",
|
|
149
|
+
"* mockstack installed with the optional `llm` dependencies:\n",
|
|
150
|
+
"\n",
|
|
151
|
+
" ```bash\n",
|
|
152
|
+
" uv pip install mockstack[llm]\n",
|
|
153
|
+
" ```\n",
|
|
154
|
+
"\n",
|
|
155
|
+
"* [ollama](https://ollama.com/) installed locally with the \"llama3.2\" model (which is typically the default model installed)"
|
|
126
156
|
]
|
|
127
157
|
},
|
|
128
158
|
{
|
|
129
159
|
"cell_type": "code",
|
|
130
160
|
"execution_count": null,
|
|
131
161
|
"metadata": {},
|
|
132
|
-
"outputs": [
|
|
133
|
-
|
|
162
|
+
"outputs": [
|
|
163
|
+
{
|
|
164
|
+
"name": "stdout",
|
|
165
|
+
"output_type": "stream",
|
|
166
|
+
"text": [
|
|
167
|
+
"Le mockstack est très amusant, mais les LLMs sont beaucoup plus froids.\n",
|
|
168
|
+
"\n",
|
|
169
|
+
"(Note: I used \"froids\" instead of \"cool\" as it's a more common translation in French. If you want to keep the exact word order and meaning, I can suggest an alternative: Le mockstack est très amusant, mais les LLMs sont vraiment cool.)\n"
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
"source": [
|
|
174
|
+
"from langchain_openai import ChatOpenAI\n",
|
|
175
|
+
"\n",
|
|
176
|
+
"\n",
|
|
177
|
+
"llm = ChatOpenAI(\n",
|
|
178
|
+
" model=\"gpt-4o\",\n",
|
|
179
|
+
" temperature=0,\n",
|
|
180
|
+
" max_tokens=None,\n",
|
|
181
|
+
" timeout=None,\n",
|
|
182
|
+
" max_retries=2,\n",
|
|
183
|
+
" base_url=\"http://localhost:8000/ollama/openai/v1\",\n",
|
|
184
|
+
" api_key=\"SOME_STRING_THAT_DOES_NOT_MATTER\",\n",
|
|
185
|
+
")\n",
|
|
186
|
+
"\n",
|
|
187
|
+
"messages = [\n",
|
|
188
|
+
" (\n",
|
|
189
|
+
" \"system\",\n",
|
|
190
|
+
" \"You are a helpful assistant that translates English to French. Translate the user sentence.\",\n",
|
|
191
|
+
" ),\n",
|
|
192
|
+
" (\"human\", \"mockstack is pretty cool. But LLMs are way cooler\"),\n",
|
|
193
|
+
"]\n",
|
|
194
|
+
"ai_msg = llm.invoke(messages)\n",
|
|
195
|
+
"\n",
|
|
196
|
+
"# Output would not be deterministic, but should be a valid French translation.\n",
|
|
197
|
+
"print(ai_msg.content)"
|
|
198
|
+
]
|
|
134
199
|
},
|
|
135
200
|
{
|
|
136
201
|
"cell_type": "markdown",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "chatcmpl-B9MBs8CjcvOU2jLn4n570S5qMJKcT",
|
|
3
|
+
"object": "chat.completion",
|
|
4
|
+
"created": 1741569952,
|
|
5
|
+
"model": "gpt-4.1-2025-04-14",
|
|
6
|
+
"choices": [
|
|
7
|
+
{
|
|
8
|
+
"index": 0,
|
|
9
|
+
"message": {
|
|
10
|
+
"role": "assistant",
|
|
11
|
+
"content": "{{ ollama(request_json.messages, 'llama3.2') | json_escape }}",
|
|
12
|
+
"refusal": null,
|
|
13
|
+
"annotations": []
|
|
14
|
+
},
|
|
15
|
+
"logprobs": null,
|
|
16
|
+
"finish_reason": "stop"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"usage": {
|
|
20
|
+
"prompt_tokens": 19,
|
|
21
|
+
"completion_tokens": 10,
|
|
22
|
+
"total_tokens": 29,
|
|
23
|
+
"prompt_tokens_details": {
|
|
24
|
+
"cached_tokens": 0,
|
|
25
|
+
"audio_tokens": 0
|
|
26
|
+
},
|
|
27
|
+
"completion_tokens_details": {
|
|
28
|
+
"reasoning_tokens": 0,
|
|
29
|
+
"audio_tokens": 0,
|
|
30
|
+
"accepted_prediction_tokens": 0,
|
|
31
|
+
"rejected_prediction_tokens": 0
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"service_tier": "default"
|
|
35
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
site_name: mockstack
|
|
2
|
-
site_url: https://
|
|
2
|
+
site_url: https://promptromp.github.io/mockstack
|
|
3
3
|
repo_name: mockstack
|
|
4
|
-
repo_url: https://github.com/
|
|
4
|
+
repo_url: https://github.com/promptromp/mockstack
|
|
5
5
|
|
|
6
6
|
theme:
|
|
7
7
|
name: material
|
|
@@ -49,10 +49,19 @@ markdown_extensions:
|
|
|
49
49
|
- pymdownx.tabbed:
|
|
50
50
|
alternate_style: true
|
|
51
51
|
|
|
52
|
+
nav:
|
|
53
|
+
- Home: README.md
|
|
54
|
+
- Configuration: configuration.md
|
|
55
|
+
- Strategies:
|
|
56
|
+
- BaseStrategy: strategies/base.md
|
|
57
|
+
- FileFixtures: strategies/filefixtures.md
|
|
58
|
+
- ProxyRules: strategies/proxyrules.md
|
|
59
|
+
- LLM Integrations:
|
|
60
|
+
- Ollama: ollama.md
|
|
52
61
|
|
|
53
62
|
extra:
|
|
54
63
|
social:
|
|
55
64
|
- icon: simple/github
|
|
56
|
-
link: https://github.com/
|
|
65
|
+
link: https://github.com/promptromp/mockstack
|
|
57
66
|
|
|
58
67
|
copyright: Copyright © 2025 Adam Ever-Hadani
|
|
@@ -20,13 +20,13 @@ from mockstack.constants import (
|
|
|
20
20
|
class OpenTelemetrySettings(BaseSettings):
|
|
21
21
|
"""Settings for OpenTelemetry."""
|
|
22
22
|
|
|
23
|
-
enabled: bool = False
|
|
23
|
+
enabled: CliImplicitFlag[bool] = False
|
|
24
24
|
|
|
25
25
|
endpoint: str = "http://localhost:4317/"
|
|
26
26
|
|
|
27
27
|
# whether to capture the response body.
|
|
28
28
|
# this can be heavy, sensitive (PII) and/or not needed depending on the use case.
|
|
29
|
-
capture_response_body: bool = False
|
|
29
|
+
capture_response_body: CliImplicitFlag[bool] = False
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class Settings(BaseSettings):
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Ollama integration"""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from ollama import chat
|
|
7
|
+
from ollama import ChatResponse
|
|
8
|
+
|
|
9
|
+
IS_OLLAMA_AVAILABLE = True
|
|
10
|
+
except ImportError:
|
|
11
|
+
IS_OLLAMA_AVAILABLE = False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if IS_OLLAMA_AVAILABLE:
|
|
15
|
+
|
|
16
|
+
class OllamaLLM:
|
|
17
|
+
def __init__(self, model: str = "llama3.2"):
|
|
18
|
+
self.model = model
|
|
19
|
+
|
|
20
|
+
def __call__(
|
|
21
|
+
self,
|
|
22
|
+
messages: List[Dict[str, str]],
|
|
23
|
+
max_tokens: int = 4096,
|
|
24
|
+
temperature: float = 0.7,
|
|
25
|
+
) -> str:
|
|
26
|
+
response: ChatResponse = chat(
|
|
27
|
+
model=self.model,
|
|
28
|
+
messages=messages,
|
|
29
|
+
options={"num_ctx": max_tokens, "temperature": temperature},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return content(response)
|
|
33
|
+
|
|
34
|
+
def content(response: ChatResponse) -> str:
|
|
35
|
+
"""Extract the message content from the LLM response."""
|
|
36
|
+
return response["message"]["content"]
|
|
37
|
+
|
|
38
|
+
def ollama(
|
|
39
|
+
messages: List[Dict[str, str]],
|
|
40
|
+
model: str = "llama3.2",
|
|
41
|
+
*args,
|
|
42
|
+
**kwargs,
|
|
43
|
+
) -> str:
|
|
44
|
+
"""Fluent interface for Ollama to be used in templates."""
|
|
45
|
+
|
|
46
|
+
return OllamaLLM(model)(
|
|
47
|
+
messages,
|
|
48
|
+
*args,
|
|
49
|
+
**kwargs,
|
|
50
|
+
)
|
|
@@ -2,21 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
from functools import cached_property
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from fastapi import HTTPException, Request, Response, status
|
|
8
|
-
from
|
|
9
|
+
from fastapi.responses import JSONResponse
|
|
10
|
+
from jinja2 import Environment
|
|
9
11
|
|
|
10
12
|
from mockstack.config import Settings
|
|
11
13
|
from mockstack.intent import (
|
|
12
14
|
looks_like_a_command,
|
|
13
15
|
looks_like_a_search,
|
|
16
|
+
wants_json,
|
|
14
17
|
)
|
|
15
18
|
from mockstack.strategies.base import BaseStrategy
|
|
16
19
|
from mockstack.strategies.create_mixin import CreateMixin
|
|
17
20
|
from mockstack.templating import (
|
|
18
21
|
iter_possible_template_arguments,
|
|
19
|
-
|
|
22
|
+
templates_env_provider,
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
|
|
@@ -37,8 +40,6 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
37
40
|
self.created_resource_metadata = settings.created_resource_metadata
|
|
38
41
|
self.missing_resource_fields = settings.missing_resource_fields
|
|
39
42
|
|
|
40
|
-
self.env = Environment(loader=FileSystemLoader(self.templates_dir))
|
|
41
|
-
|
|
42
43
|
def __str__(self) -> str:
|
|
43
44
|
return (
|
|
44
45
|
f"[medium_purple]filefixtures[/medium_purple]\n "
|
|
@@ -46,6 +47,11 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
46
47
|
f"enable_templates_for_post: [medium_purple]{self.enable_templates_for_post}[/medium_purple]. "
|
|
47
48
|
)
|
|
48
49
|
|
|
50
|
+
@cached_property
|
|
51
|
+
def env(self) -> Environment:
|
|
52
|
+
"""Jinja2 environment for the filefixtures strategy."""
|
|
53
|
+
return templates_env_provider(self.templates_dir)
|
|
54
|
+
|
|
49
55
|
async def apply(self, request: Request) -> Response:
|
|
50
56
|
match request.method:
|
|
51
57
|
case "GET":
|
|
@@ -74,9 +80,10 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
74
80
|
We also allow a configuration to specify a default intent.
|
|
75
81
|
|
|
76
82
|
"""
|
|
83
|
+
request_json = (await request.json()) if wants_json(request) else None
|
|
77
84
|
if self.enable_templates_for_post:
|
|
78
85
|
try:
|
|
79
|
-
return self._response_from_template(request)
|
|
86
|
+
return self._response_from_template(request, request_json=request_json)
|
|
80
87
|
except HTTPException as e:
|
|
81
88
|
if e.status_code == status.HTTP_404_NOT_FOUND:
|
|
82
89
|
# If the template is not found, we try to create the resource with logic below.
|
|
@@ -86,12 +93,12 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
86
93
|
|
|
87
94
|
if looks_like_a_search(request):
|
|
88
95
|
# Searching for resources with a complex query that cannot be expressed in a URI.
|
|
89
|
-
return self._response_from_template(request)
|
|
96
|
+
return self._response_from_template(request, request_json=request_json)
|
|
90
97
|
elif looks_like_a_command(request):
|
|
91
98
|
# Executing a 'command' of some sort, like a workflow or a batch job.
|
|
92
99
|
# We return a 201 CREATED status code with response from template.
|
|
93
100
|
return self._response_from_template(
|
|
94
|
-
request, status_code=status.HTTP_201_CREATED
|
|
101
|
+
request, request_json=request_json, status_code=status.HTTP_201_CREATED
|
|
95
102
|
)
|
|
96
103
|
else:
|
|
97
104
|
# simulate resource creation:
|
|
@@ -133,9 +140,15 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
133
140
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
134
141
|
|
|
135
142
|
def _response_from_template(
|
|
136
|
-
self,
|
|
143
|
+
self,
|
|
144
|
+
request: Request,
|
|
145
|
+
*,
|
|
146
|
+
request_json: dict | None = None,
|
|
147
|
+
status_code: int = status.HTTP_200_OK,
|
|
137
148
|
) -> Response:
|
|
138
|
-
for template_args in iter_possible_template_arguments(
|
|
149
|
+
for template_args in iter_possible_template_arguments(
|
|
150
|
+
request, request_json=request_json
|
|
151
|
+
):
|
|
139
152
|
filename = self.templates_dir / template_args["name"]
|
|
140
153
|
self.logger.debug("Looking for template filename: %s", filename)
|
|
141
154
|
if not os.path.exists(filename):
|
|
@@ -152,17 +165,10 @@ class FileFixturesStrategy(BaseStrategy, CreateMixin):
|
|
|
152
165
|
)
|
|
153
166
|
|
|
154
167
|
# if we get here, we have no template to render.
|
|
155
|
-
raise HTTPException(
|
|
156
|
-
status_code=status.HTTP_404_NOT_FOUND,
|
|
157
|
-
detail=missing_template_detail(request, templates_dir=self.templates_dir),
|
|
158
|
-
)
|
|
159
|
-
"""
|
|
160
|
-
# TODO: return custom fields from settings
|
|
161
168
|
return JSONResponse(
|
|
162
169
|
content=self.missing_resource_fields,
|
|
163
170
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
164
171
|
)
|
|
165
|
-
"""
|
|
166
172
|
|
|
167
173
|
def update_opentelemetry(self, request: Request, template_args: dict) -> None:
|
|
168
174
|
"""Update the opentelemetry span with the file fixtures details."""
|