Skip to content

Commit 81d796f

Browse files
authored
Support OpenAI embeddings endpoint (#408)
1 parent b5ecbd1 commit 81d796f

27 files changed

+1148
-75
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,19 @@ It supports mocking following AI services:
161161
4. [Ollama](https://github.com/ollama/ollama/blob/main/docs/api.md) - [ai-mocks-ollama](https://mokksy.dev/docs/ai-mocks/ollama/)
162162
5. [Agent-to-Agent (A2A) Protocol](https://a2a-protocol.org/latest/specification/) - [ai-mocks-a2a](https://mokksy.dev/docs/ai-mocks/a2a/)
163163

164-
**_NB! Not all API endpoints and parameters are supported!_**
164+
## Feature Support Matrix
165+
166+
| Feature | OpenAI | Anthropic | Gemini | Ollama | A2A |
167+
|----------------------|-----------|-----------|--------|----------|--------------------------------------|
168+
| **Chat Completions** ||||||
169+
| **Streaming** ||||||
170+
| **Embeddings** ||||||
171+
| **Moderation** ||||||
172+
| **Additional APIs** | Responses | - | - | Generate | Full A2A Protocol<br/>(11 endpoints) |
173+
174+
175+
## Enjoying LLM integration testing? ❤️
176+
[![Buy me a Coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://buymeacoffee.com/mailsk)
165177

166178
## How to build
167179

@@ -181,6 +193,3 @@ make
181193

182194
I do welcome contributions! Please see the [Contributing Guidelines](CONTRIBUTING.md) for details.
183195

184-
## Enjoying LLM integration testing? :heart:
185-
186-
[![Buy me a Coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://buymeacoffee.com/mailsk)

ai-mocks-ollama/src/commonMain/kotlin/me/kpavlov/aimocks/ollama/MockOllama.kt

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import me.kpavlov.aimocks.ollama.generate.GenerateRequest
1515
import me.kpavlov.aimocks.ollama.generate.OllamaGenerateBuildingStep
1616
import me.kpavlov.aimocks.ollama.generate.OllamaGenerateRequestSpecification
1717
import me.kpavlov.mokksy.ServerConfiguration
18-
import java.util.function.Consumer
1918

2019
/**
2120
* Mock implementation of an Ollama-compatible service for testing purposes.
@@ -47,21 +46,6 @@ public open class MockOllama(
4746
)
4847
},
4948
) {
50-
/**
51-
* Configures a mock `/api/generate` endpoint using a Java `Consumer` to specify the request criteria.
52-
*
53-
* This overload enables Java interoperability for setting up request matching
54-
* and response configuration for the generate API.
55-
*
56-
* @param name An optional name for the mock configuration.
57-
* @param block A Java `Consumer` that configures the generate request specification.
58-
* @return A building step for further response configuration.
59-
*/
60-
@JvmOverloads
61-
public fun generate(
62-
name: String? = null,
63-
block: Consumer<OllamaGenerateRequestSpecification>,
64-
): OllamaGenerateBuildingStep = generate(name) { block.accept(this) }
6549

6650
/**
6751
* Sets up a mock handler for the Ollama `/api/generate` completion endpoint.
@@ -111,19 +95,6 @@ public open class MockOllama(
11195
)
11296
}
11397

114-
/**
115-
* Provides a Java-compatible overload for configuring a mock Ollama chat request using a Consumer.
116-
*
117-
* @param name An optional name for the mock configuration.
118-
* @param block A Consumer that configures the chat request specification.
119-
* @return A building step for further configuration of the mock chat response.
120-
*/
121-
@JvmOverloads
122-
public fun chat(
123-
name: String? = null,
124-
block: Consumer<OllamaChatRequestSpecification>,
125-
): OllamaChatBuildingStep = chat(name) { block.accept(this) }
126-
12798
/**
12899
* Sets up a mock handler for the Ollama chat completion (`/api/chat`) endpoint.
129100
*
@@ -181,21 +152,6 @@ public open class MockOllama(
181152
)
182153
}
183154

184-
/**
185-
* Provides a Java-compatible overload for configuring a mock `/api/embed` endpoint using a Consumer.
186-
*
187-
* Allows Java code to specify the embedding request specification for the mock Ollama embed API.
188-
*
189-
* @param name Optional name for the mock configuration.
190-
* @param block Consumer that configures the embedding request specification.
191-
* @return A building step for further response configuration.
192-
*/
193-
@JvmOverloads
194-
public fun embed(
195-
name: String? = null,
196-
block: Consumer<OllamaEmbedRequestSpecification>,
197-
): OllamaEmbedBuildingStep = embed(name) { block.accept(this) }
198-
199155
/**
200156
* Sets up a mock handler for the Ollama `/api/embed` endpoint,
201157
* allowing configuration of request matching for embedding requests.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package me.kpavlov.aimocks.ollama
2+
3+
import me.kpavlov.aimocks.ollama.chat.OllamaChatBuildingStep
4+
import me.kpavlov.aimocks.ollama.chat.OllamaChatRequestSpecification
5+
import me.kpavlov.aimocks.ollama.embed.OllamaEmbedBuildingStep
6+
import me.kpavlov.aimocks.ollama.embed.OllamaEmbedRequestSpecification
7+
import me.kpavlov.aimocks.ollama.generate.OllamaGenerateBuildingStep
8+
import me.kpavlov.aimocks.ollama.generate.OllamaGenerateRequestSpecification
9+
import java.util.function.Consumer
10+
11+
/**
12+
* Provides a Java-compatible overload for configuring a mock Ollama chat request using a Consumer.
13+
*
14+
* @param name An optional name for the mock configuration.
15+
* @param block A Consumer that configures the chat request specification.
16+
* @return A building step for further configuration of the mock chat response.
17+
*/
18+
@JvmOverloads
19+
public fun MockOllama.chat(
20+
name: String? = null,
21+
block: Consumer<OllamaChatRequestSpecification>,
22+
): OllamaChatBuildingStep = chat(name) { block.accept(this) }
23+
24+
/**
25+
* Configures a mock `/api/generate` endpoint using a Java `Consumer` to specify the request criteria.
26+
*
27+
* This overload enables Java interoperability for setting up request matching
28+
* and response configuration for the generate API.
29+
*
30+
* @param name An optional name for the mock configuration.
31+
* @param block A Java `Consumer` that configures the generate request specification.
32+
* @return A building step for further response configuration.
33+
*/
34+
@JvmOverloads
35+
public fun MockOllama.generate(
36+
name: String? = null,
37+
block: Consumer<OllamaGenerateRequestSpecification>,
38+
): OllamaGenerateBuildingStep = generate(name) { block.accept(this) }
39+
40+
41+
/**
42+
* Provides a Java-compatible overload for configuring a mock `/api/embed` endpoint using a Consumer.
43+
*
44+
* Allows Java code to specify the embedding request specification for the mock Ollama embed API.
45+
*
46+
* @param name Optional name for the mock configuration.
47+
* @param block Consumer that configures the embedding request specification.
48+
* @return A building step for further response configuration.
49+
*/
50+
@JvmOverloads
51+
public fun MockOllama.embed(
52+
name: String? = null,
53+
block: Consumer<OllamaEmbedRequestSpecification>,
54+
): OllamaEmbedBuildingStep = embed(name) { block.accept(this) }

ai-mocks-openai/src/commonMain/kotlin/me/kpavlov/aimocks/openai/MockOpenai.kt

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package me.kpavlov.aimocks.openai
22

33
import io.kotest.assertions.json.containJsonKeyValue
4+
import io.kotest.matchers.string.contain
45
import io.ktor.serialization.kotlinx.json.json
56
import kotlinx.serialization.json.Json
67
import me.kpavlov.aimocks.core.AbstractMockLlm
78
import me.kpavlov.aimocks.openai.completions.OpenaiChatCompletionRequestSpecification
89
import me.kpavlov.aimocks.openai.completions.OpenaiChatCompletionsBuildingStep
10+
import me.kpavlov.aimocks.openai.embeddings.OpenaiEmbedBuildingStep
11+
import me.kpavlov.aimocks.openai.embeddings.OpenaiEmbedRequestSpecification
12+
import me.kpavlov.aimocks.openai.model.embeddings.CreateEmbeddingsRequest
913
import me.kpavlov.aimocks.openai.model.moderation.CreateModerationRequest
1014
import me.kpavlov.aimocks.openai.model.responses.CreateResponseRequest
1115
import me.kpavlov.aimocks.openai.moderation.OpenaiModerationBuildingStep
@@ -25,6 +29,7 @@ import java.util.function.Consumer
2529
* @param port The port on which the mock server will run. Defaults to 0, which allows the server to select
2630
* an available port.
2731
* @param verbose Controls whether the mock server's operations are logged in detail. Defaults to true.
32+
* @see <a href="https://platform.openai.com/docs/api-reference">OpenAI API Reference</a>
2833
* @author Konstantin Pavlov
2934
*/
3035
public open class MockOpenai(
@@ -51,6 +56,19 @@ public open class MockOpenai(
5156
block: Consumer<OpenaiChatCompletionRequestSpecification>,
5257
): OpenaiChatCompletionsBuildingStep = completion(name) { block.accept(this) }
5358

59+
/**
60+
* Configures and constructs a mock handler for the OpenAI `/v1/chat/completions` endpoint.
61+
*
62+
* This method allows you to define specifications and criteria for chat completion requests,
63+
* enabling controlled responses for testing and simulations.
64+
*
65+
* @param name An optional identifier for the mock configuration. Defaults to `null` if not provided.
66+
* @param block A lambda function to configure the specifications for the chat completion request,
67+
* using the `OpenaiChatCompletionRequestSpecification` object.
68+
* @return An instance of `OpenaiChatCompletionsBuildingStep`, representing a builder step
69+
* for configuring mock response behavior.
70+
* @see <a href="https://platform.openai.com/docs/api-reference/chat/create">Create Chat Completion</a>
71+
*/
5472
public fun completion(
5573
name: String? = null,
5674
block: OpenaiChatCompletionRequestSpecification.() -> Unit,
@@ -94,6 +112,17 @@ public open class MockOpenai(
94112
)
95113
}
96114

115+
/**
116+
* Sets up a mock handler for the OpenAI `/v1/responses` endpoint,
117+
* allowing configuration of request matching for response generation requests.
118+
*
119+
* This endpoint is used to generate responses based on input files and instructions.
120+
*
121+
* @param name Optional identifier for the mock configuration.
122+
* @param block Lambda to configure the request matching criteria.
123+
* @return A builder step for specifying the mock response to response generation requests.
124+
* @see <a href="https://platform.openai.com/docs/api-reference/responses/create">OpenAI Responses API</a>
125+
*/
97126
public fun responses(
98127
name: String? = null,
99128
block: OpenaiResponsesRequestSpecification.() -> Unit,
@@ -133,12 +162,30 @@ public open class MockOpenai(
133162
)
134163
}
135164

165+
/**
166+
* Java-friendly overload that accepts a Consumer for configuring the moderation request.
167+
*
168+
* @param name Optional identifier for the mock configuration.
169+
* @param block Consumer to configure the request matching criteria.
170+
* @return A builder step for specifying the mock response to moderation requests.
171+
*/
136172
@JvmOverloads
137173
public fun moderation(
138174
name: String? = null,
139175
block: Consumer<OpenaiModerationRequestSpecification>,
140176
): OpenaiModerationBuildingStep = moderation(name) { block.accept(this) }
141177

178+
/**
179+
* Sets up a mock handler for the OpenAI `/v1/moderations` endpoint,
180+
* allowing configuration of request matching for moderation requests.
181+
*
182+
* This endpoint classifies if input text or images violate OpenAI's usage policies.
183+
*
184+
* @param name Optional identifier for the mock configuration.
185+
* @param block Lambda to configure the request matching criteria.
186+
* @return A builder step for specifying the mock response to moderation requests.
187+
* @see <a href="https://platform.openai.com/docs/api-reference/moderations/create">OpenAI Moderations API</a>
188+
*/
142189
public fun moderation(
143190
name: String? = null,
144191
block: OpenaiModerationRequestSpecification.() -> Unit,
@@ -170,5 +217,82 @@ public open class MockOpenai(
170217
)
171218
}
172219

220+
/**
221+
* Java-friendly overload that accepts a Consumer for configuring the embedding request.
222+
*
223+
* @param name Optional identifier for the mock configuration.
224+
* @param block Consumer to configure the request matching criteria.
225+
* @return A builder step for specifying the mock response to embedding requests.
226+
*/
227+
@JvmOverloads
228+
public fun embeddings(
229+
name: String? = null,
230+
block: Consumer<OpenaiEmbedRequestSpecification>,
231+
): OpenaiEmbedBuildingStep = embeddings(name) { block.accept(this) }
232+
233+
/**
234+
* Sets up a mock handler for the OpenAI `/v1/embeddings` endpoint,
235+
* allowing configuration of request matching for embedding requests.
236+
*
237+
* Supports matching on model, input (string or list), dimensions, encoding_format,
238+
* and user fields in the request body.
239+
*
240+
* @param name Optional identifier for the mock configuration.
241+
* @param block Lambda to configure the request matching criteria.
242+
* @return A builder step for specifying the mock response to embedding requests.
243+
* @see <a href="https://platform.openai.com/docs/api-reference/embeddings/create">OpenAI Embeddings API</a>
244+
*/
245+
@JvmOverloads
246+
public fun embeddings(
247+
name: String? = null,
248+
block: OpenaiEmbedRequestSpecification.() -> Unit,
249+
): OpenaiEmbedBuildingStep {
250+
val requestStep =
251+
mokksy.post(
252+
name = name,
253+
requestType = CreateEmbeddingsRequest::class,
254+
) {
255+
val embedRequestSpec = OpenaiEmbedRequestSpecification()
256+
block(embedRequestSpec)
257+
258+
path("/v1/embeddings")
259+
260+
embedRequestSpec.model?.let {
261+
bodyString += containJsonKeyValue("model", it)
262+
}
263+
264+
// Handle string input
265+
embedRequestSpec.stringInput?.let {
266+
bodyString += containJsonKeyValue("input", it)
267+
}
268+
269+
// Handle string list input
270+
embedRequestSpec.stringListInput?.let {
271+
// For list inputs, we can't use containJsonKeyValue directly.
272+
// Instead, we'll check that the request body contains the input values
273+
it.forEach { inputValue ->
274+
bodyString += contain(inputValue)
275+
}
276+
}
277+
278+
embedRequestSpec.user?.let {
279+
bodyString += containJsonKeyValue("user", it)
280+
}
281+
282+
embedRequestSpec.requestBodyString.forEach {
283+
bodyString += contain(it)
284+
}
285+
}
286+
287+
return OpenaiEmbedBuildingStep(
288+
buildingStep = requestStep,
289+
mokksy = mokksy,
290+
)
291+
}
292+
293+
public fun MockOpenai.moderation(
294+
block: Consumer<OpenaiModerationRequestSpecification>,
295+
): OpenaiModerationBuildingStep = moderation(block)
296+
173297
override fun baseUrl(): String = "http://localhost:${port()}/v1"
174298
}

0 commit comments

Comments
 (0)