- [Overview](#overview)
Table of Contents
- Overview
- ApiMode
- Public Functions
- GenericOpenAIEnv
- ToolDefinition / FunctionSchema
- ResponseFormat
- CacheControl
- Error Mapping
- Multimodal Content
- Next Steps
Overview
The GenericOpenAIPipe class provides a TPipe abstraction for OpenAI-compatible APIs. It supports three API modes — OpenAI Chat Completions, Anthropic Messages, and OpenAI Responses — and works with any provider that implements one of these specifications.
class GenericOpenAIPipe : Pipe()
Requirements: Call setApiKey() before init(), or set the GENERIC_OPENAI_API_KEY environment variable.
Example — OpenAI mode (default):
val pipe = GenericOpenAIPipe()
.setApiKey(System.getenv("OPENAI_API_KEY"))
.setModel("gpt-4o")
.setSystemPrompt("You are a helpful assistant.")
.init()
val result = pipe.execute("What is TPipe?")
Example — Anthropic mode:
val pipe = GenericOpenAIPipe()
.setApiKey(System.getenv("ANTHROPIC_API_KEY"))
.setBaseUrl("https://api.anthropic.com")
.setModel("claude-3-5-sonnet-20241022")
.setApiMode(ApiMode.Anthropic)
.init()
Example — OpenAI Responses mode:
val pipe = GenericOpenAIPipe()
.setApiKey(System.getenv("OPENAI_API_KEY"))
.setModel("gpt-4o-2025-04-16")
.setApiMode(ApiMode.OpenAIResponses)
.init()
ApiMode
ApiMode is a sealed class that selects which wire format, endpoint, request serializer, response parser, SSE parser, and auth header set the pipe uses. The default is ApiMode.OpenAI.
sealed class ApiMode
{
data object OpenAI : ApiMode()
data object Anthropic : ApiMode()
data object OpenAIResponses : ApiMode()
companion object { val DEFAULT: ApiMode = OpenAI }
}
| Mode | Endpoint | Auth Header |
|---|---|---|
ApiMode.OpenAI (default) | ${baseUrl}/chat/completions | Authorization: Bearer <key> |
ApiMode.Anthropic | ${baseUrl}/anthropic/v1/messages | x-api-key: <key>, anthropic-version: 2023-06-01 |
ApiMode.OpenAIResponses | ${baseUrl}/responses | Authorization: Bearer <key> |
⚠
apiModeis locked after the first API call. OncesendRequest(...)runs (i.e. after the firstexecute()/generateText()/generateContent()), the internalapiModeLockedflag flips totrue. CallingsetApiMode(...)afterwards throwsIllegalStateException("apiMode cannot be changed after the first API request"). Set the mode up front, before the first call, and create a new pipe instance if you need a different mode.
OpenAIResponses is a sealed data object that targets OpenAI’s newer Responses wire spec — top-level instructions, input items, and a streaming protocol driven by response.created / response.output_text.delta / response.completed events. See OpenAIResponsesRequestSerializer / OpenAIResponsesSseParser for the wire details.
Public Functions
Authentication & Endpoint
setApiKey(key: String): GenericOpenAIPipe
Sets the API key. Required unless GENERIC_OPENAI_API_KEY environment variable is set. The pipe-level value is checked first; if blank, init() falls back to GenericOpenAIEnv.resolveApiKey().
setBaseUrl(url: String): GenericOpenAIPipe
Sets the base URL. Defaults to https://api.openai.com/v1. Must use HTTPS — passing an http:// URL throws IllegalArgumentException("baseUrl must use HTTPS for security"). Trailing slashes are stripped.
Example:
.setBaseUrl("https://api.openai.com/v1") // OpenAI (default)
.setBaseUrl("https://api.anthropic.com") // Anthropic
.setBaseUrl("https://openai.myenterprise.com") // Third-party proxy
Bedrock Mantle
Amazon Bedrock Mantle is a regional Bedrock endpoint surface that exposes selected foundation models over an OpenAI-compatible HTTP wire format. The Mantle endpoint pattern is https://bedrock-mantle.{region}.api.aws/openai/v1. Mantle models are reached through GenericOpenAIPipe, not through BedrockPipe — Mantle does not speak the AWS Converse API.
Three builders configure Mantle on a pipe. The first two set the endpoint, region, and API mode; the third overrides the authentication shape that the pipe resolves from environment variables.
setBedrockMantle(region: String, modelId: String): GenericOpenAIPipe
Wires the pipe to the Mantle endpoint at https://bedrock-mantle.{region}.api.aws/openai/v1, sets apiMode to ApiMode.OpenAI, and resolves authentication. region is the AWS region code (e.g. us-east-2); modelId is the Bedrock model identifier (e.g. google.gemma-4-31b).
Example — Bearer auth via env var:
val pipe = GenericOpenAIPipe()
.setBedrockMantle(region = "us-east-2", modelId = "google.gemma-4-31b")
.setMaxTokens(64)
.init()
Example — SigV4 auth via standard AWS env vars (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY):
val pipe = GenericOpenAIPipe()
.setBedrockMantle(region = "us-east-2", modelId = "google.gemma-4-31b")
.init()
// pipe automatically picks SigV4 from AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY
setBedrockMantleWithResponses(region: String, modelId: String): GenericOpenAIPipe
Mirrors setBedrockMantle but selects ApiMode.OpenAIResponses. Requests dispatch to ${baseUrl}/responses and the parser accepts both OpenAI’s response.reasoning_text.delta and Mantle’s shorter response.reasoning.delta SSE event names.
Example — Responses API with reasoning:
val pipe = GenericOpenAIPipe()
.setBedrockMantleWithResponses(region = "us-east-2", modelId = "google.gemma-4-31b")
.setMaxTokens(8192)
.setReasoningConfig(ReasoningConfig(effort = "high"))
.init()
setBedrockMantleAuth(auth: BedrockMantleAuth?): GenericOpenAIPipe
Replaces the Mantle authentication shape that the pipe resolves from BedrockMantleEnv. Pass null to clear any previously-set Mantle auth and fall back to the bearer/x-api-key defaults produced by the generic getAuthHeaders path. The BedrockMantleAuth sealed class has three variants:
BedrockMantleAuth.Bearer(apiKey)— Bearer token authentication using a Bedrock API key. SendsAuthorization: Bearer <apiKey>.BedrockMantleAuth.SigV4(signer)— AWS SigV4 for non-streaming requests.BedrockMantleAuth.Streaming(initialSigner, chunkedSigner, decodedContentLength)— AWS SigV4 chunked-encoding for streaming requests.
The pipe picks Streaming automatically when streaming is enabled and SigV4 credentials are resolvable; it picks SigV4 for non-streaming when SigV4 credentials are resolvable; otherwise it falls back to Bearer. Explicit setBedrockMantleAuth(...) overrides the auto-resolution.
Example — explicit Bearer auth from a Bedrock API key:
val pipe = GenericOpenAIPipe()
.setBedrockMantle(region = "us-east-2", modelId = "google.gemma-4-31b")
.setBedrockMantleAuth(BedrockMantleAuth.bearer(System.getenv("BEDROCK_MANTLE_API_KEY")))
.init()
IAM actions for SigV4: the AWS service identifier is
bedrock-mantle. Required actions are documented in the AWS Bedrock Mantle IAM reference; the typical minimum isbedrock-mantle:CreateInferenceplus the standardGet*/List*actions for the model you are calling. Bearer-key users authenticate through the Bedrock API key gateway and do not require IAM permissions on the caller.
The full reference for BedrockMantleAuth (every sealed-class variant, every companion-object factory, the SigV4 streaming chunk wire format), BedrockMantleConfiguration, and BedrockMantleEnv (env-var precedence) lives in docs/api/bedrock-mantle.md. The provider-level getting-started — env-var resolution chain, model catalog, streaming and reasoning examples, error mapping — lives in docs/bedrock/mantle.md.
API Mode
setApiMode(mode: ApiMode): GenericOpenAIPipe
Sets the wire format and endpoint. Default is ApiMode.OpenAI. Throws IllegalStateException if called after the first API request.
Parameters:
ApiMode.OpenAI— OpenAI Chat Completions format at${baseUrl}/chat/completionsApiMode.Anthropic— Anthropic messages format at${baseUrl}/anthropic/v1/messagesApiMode.OpenAIResponses— OpenAI Responses format at${baseUrl}/responses
Example:
.setApiMode(ApiMode.Anthropic)
Inference Settings
setFrequencyPenalty(penalty: Double): GenericOpenAIPipe
Sets frequency penalty (-2.0 to 2.0). Reduces repetition of tokens proportional to their prior frequency.
setModalities(modalities: List<String>): GenericOpenAIPipe
Sets output modalities (e.g., ["text", "image", "audio"]) for multimodal models. Serialized to the modalities field of the request body when set.
Function Calling
setTools(tools: List<ToolDefinition>): GenericOpenAIPipe
Registers function definitions for tool-calling models. See ToolDefinition / FunctionSchema for the data class shape.
Example:
import genericOpenAIPipe.env.ToolDefinition
import genericOpenAIPipe.env.FunctionSchema
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
val tools = listOf(
ToolDefinition(
type = "function",
function = FunctionSchema(
name = "get_weather",
description = "Get current weather",
parameters = Json.parseToJsonElement("""{
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}""").jsonObject
)
)
)
pipe.setTools(tools)
setToolChoice(choice: String): GenericOpenAIPipe
Sets the tool choice mode: "auto", "none", or "required".
setParallelToolCalls(enabled: Boolean): GenericOpenAIPipe
Enables or disables parallel function calling (default: true).
Structured Output
setResponseFormat(type: String, jsonSchema: kotlinx.serialization.json.JsonObject? = null): GenericOpenAIPipe
Sets the response format for structured output. Internally stores a ResponseFormat instance.
"text"— plain text (default)"json_object"— JSON object mode"json_schema"— requiresjsonSchemaparameter
setStructuredOutputs(enabled: Boolean): GenericOpenAIPipe
Enables structured outputs via json_schema. Must be used with setResponseFormat("json_schema", schema).
Streaming
setStreamingEnabled(enabled: Boolean): GenericOpenAIPipe
Enables Server-Sent Events (SSE) streaming. When enabled, the response is delivered as a series of chunks. The active ApiMode selects the SSE parser:
ApiMode.OpenAI→SseParserApiMode.Anthropic→AnthropicSseParserApiMode.OpenAIResponses→OpenAIResponsesSseParser
setStreamingCallback(callback: suspend (String) -> Unit, propagateToChildren: Boolean = true, propagateToReasoning: Boolean = true): GenericOpenAIPipe
Registers a callback for streaming response chunks. Automatically enables streaming by flipping streamingEnabled = true, adding the callback via obtainStreamingCallbackManager(), and propagating the callback to descendant pipes (validator, transformation, branch, reasoning) via propagateStreamingCallback. This ensures chunks emitted by any pipe in the tree flow through the registered callback.
Parameters:
callback: Suspendable callback receiving text chunks as they arrivepropagateToChildren: Whether to propagate to validator, transformation, and branch pipes. Defaults totrue. Set tofalseto restrict propagation to the reasoning pipe only.propagateToReasoning: Whether to propagate to the reasoning pipe. Defaults totrue. Set tofalseto exclude the reasoning pipe.
Example — default propagation (all descendants):
pipe.setStreamingCallback { chunk ->
print(chunk)
flush()
}
Example — propagate to children but not to reasoning:
pipe.setStreamingCallback(
{ chunk -> print(chunk) },
propagateToChildren = true,
propagateToReasoning = false
)
Example — propagate to reasoning only:
pipe.setStreamingCallback(
{ chunk -> display(chunk) },
propagateToChildren = false,
propagateToReasoning = true
)
See Also: Streaming Callbacks Guide
Reasoning
setReasoningConfig(config: ReasoningConfig): GenericOpenAIPipe
Configures reasoning for capable models (e.g., o3, o4-mini, DeepSeek-R1). Serialized into the request body for the active mode.
data class ReasoningConfig(
val effort: String? = null, // "xhigh", "high", "medium", "low", "minimal", "none"
@SerialName("max_tokens")
val maxTokens: Int? = null, // max tokens for reasoning output
val exclude: Boolean? = null, // exclude reasoning from final output
val enabled: Boolean? = null // enable/disable reasoning
)
Example:
import genericOpenAIPipe.env.ReasoningConfig
pipe.setReasoningConfig(ReasoningConfig(
effort = "high",
maxTokens = 8192,
exclude = false,
enabled = true
))
Prompt Caching
setCacheControl(type: String = "ephemeral", ttl: String? = null): GenericOpenAIPipe
Enables explicit prompt caching on the Anthropic API path. The cache breakpoint is placed on the last system block, caching the full system prompt prefix (tools + system) per the Anthropic/MiniMax spec.
Supported models: MiniMax-M2.7, M2.5, M2.1, M2. Not supported on M3 — use passive auto-cache on ApiMode.OpenAI instead.
TTL behavior by provider:
| Provider | TTL support |
|---|---|
| Direct Anthropic API | "5m" (default, 5 min) or "1h" (1 hour) |
MiniMax /anthropic endpoint | TTL ignored — cache is always 5 minutes, auto-refreshes on hit at no additional cost |
Example — MiniMax (no TTL):
val pipe = GenericOpenAIPipe()
.setApiKey(System.getenv("MINIMAX_API_KEY"))
.setBaseUrl("https://api.minimax.io")
.setModel("MiniMax-M2.7")
.setApiMode(ApiMode.Anthropic)
.setSystemPrompt(systemPrompt)
.setCacheControl() // ttl omitted — MiniMax uses 5 min default
.init()
Example — Direct Anthropic (with 1h TTL):
val pipe = GenericOpenAIPipe()
.setApiKey(System.getenv("ANTHROPIC_API_KEY"))
.setBaseUrl("https://api.anthropic.com")
.setModel("claude-3-5-sonnet-20241022")
.setApiMode(ApiMode.Anthropic)
.setSystemPrompt(systemPrompt)
.setCacheControl(ttl = "1h") // 1-hour cache on Anthropic
.init()
Note: Passive auto-cache (on ApiMode.OpenAI) requires no code — MiniMax automatically caches at 512+ input tokens at no cost. Use explicit setCacheControl only when you need the longer TTL available on direct Anthropic, or when targeting M2-family models that support it.
Resource Management
init(): Pipe
Initializes the pipe. Validates configuration and sets up the HTTP client.
Behavior:
- Resolves API key from parameter or
GenericOpenAIEnv.resolveApiKey()(which checks the env varGENERIC_OPENAI_API_KEY) - Sets
provider = ProviderName.Gpt - Creates a CIO-based HTTP client with 120s request timeout, 30s connect timeout, 120s socket timeout
- Emits a
TraceEventType.PIPE_STARTtrace event taggedprovider = "GenericOpenAI"
Throws: IllegalStateException("GenericOpenAI API key is required. Call setApiKey(), genericOpenAIEnv.setApiKey(), or set GENERIC_OPENAI_API_KEY environment variable before init().") if no API key is configured.
abort()
Closes the HTTP client and cleans up resources. Emits a TraceEventType.PIPE_FAILURE trace event with action = "abort". Call when done with the pipe.
GenericOpenAIEnv
The GenericOpenAIEnv object is a process-wide singleton that owns the API key fallback chain. It lives in genericOpenAIPipe.env:
package genericOpenAIPipe.env
object GenericOpenAIEnv
{
fun setApiKey(key: String)
fun getApiKey(): String
fun getApiKeyFromEnv(): String
fun resolveApiKey(): String
fun clearApiKey()
fun hasApiKey(): Boolean
}
setApiKey(key: String)
Sets the Generic OpenAI API key programmatically. Persists for the lifetime of the process unless clearApiKey() is called.
import genericOpenAIPipe.env.GenericOpenAIEnv
GenericOpenAIEnv.setApiKey("sk-...")
getApiKey(): String
Returns the currently configured API key, or empty string if not set. Reads the in-process field only — does not consult the environment variable.
getApiKeyFromEnv(): String
Returns the API key from the GENERIC_OPENAI_API_KEY environment variable, or empty string if not set.
resolveApiKey(): String
Returns the effective API key: programmatic value if non-blank, otherwise the environment variable. This is the value init() uses as its fallback.
clearApiKey()
Clears the programmatically set API key. After this call, only the environment variable is used.
hasApiKey(): Boolean
Returns true if any API key is available (programmatic or env var). Useful for pre-flight checks before constructing a pipe.
Usage pattern:
import genericOpenAIPipe.env.GenericOpenAIEnv
import genericOpenAIPipe.GenericOpenAIPipe
if (!GenericOpenAIEnv.hasApiKey()) {
GenericOpenAIEnv.setApiKey(System.getenv("OPENAI_API_KEY"))
}
val pipe = GenericOpenAIPipe()
.setModel("gpt-4o")
.init()
The init() chain is: pipe-level apiKey field → GenericOpenAIEnv.resolveApiKey() → IllegalStateException.
ToolDefinition / FunctionSchema
setTools(...) accepts a list of ToolDefinition data classes. These are the function-calling payloads serialized to the tools array of the request body.
package genericOpenAIPipe.env
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
data class ToolDefinition(
val type: String = "function",
val function: FunctionSchema
)
@Serializable
data class FunctionSchema(
val name: String,
val description: String,
val parameters: JsonObject
)
| Field | Type | Required | Description |
|---|---|---|---|
ToolDefinition.type | String | optional, default "function" | Tool type discriminator. Only "function" is supported today. |
ToolDefinition.function | FunctionSchema | yes | The callable function schema. |
FunctionSchema.name | String | yes | Function name; must match across calls. |
FunctionSchema.description | String | yes | Natural-language description of the function. The model uses this to decide when to call it. |
FunctionSchema.parameters | JsonObject | yes | Standard JSON Schema describing the function’s parameters. |
Pair setTools(...) with setToolChoice("auto" | "none" | "required") to control when the model may call, and setParallelToolCalls(true | false) to allow multiple tool calls in a single response.
ResponseFormat
setResponseFormat(type, jsonSchema) constructs a ResponseFormat instance and stores it on the pipe. It is serialized into the response_format field of the request body.
package genericOpenAIPipe.env
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
data class ResponseFormat(
val type: String, // "text", "json_object", or "json_schema"
val jsonSchema: JsonObject? = null
)
| Field | Type | Required | Description |
|---|---|---|---|
type | String | yes | Format type. Use "text" for plain prose, "json_object" for free-form JSON, "json_schema" for schema-validated JSON. |
jsonSchema | JsonObject? | required when type == "json_schema" | A standard JSON Schema describing the desired output shape. |
For schema-validated output, also call setStructuredOutputs(true):
val schema = Json.parseToJsonElement("""{
"type": "object",
"properties": { "answer": { "type": "string" } },
"required": ["answer"]
}""").jsonObject
pipe.setResponseFormat("json_schema", schema)
pipe.setStructuredOutputs(true)
CacheControl
Anthropic-style prompt caching hint carried on the wire-level request. Defined in genericOpenAIPipe.env:
package genericOpenAIPipe.env
import kotlinx.serialization.Serializable
@Serializable
data class CacheControl(
val type: String, // e.g., "ephemeral"
val ttl: String? = null // e.g., "5m", "1h", "24h"
)
| Field | Type | Required | Description |
|---|---|---|---|
type | String | yes | Cache type discriminator. Common value: "ephemeral". |
ttl | String? | optional | Cache time-to-live. Provider-accepted values include "5m", "1h", "24h". |
CacheControl is a field on GenericOpenAIChatRequest (serialized as cache_control) and is plumbed through generateText(...) and generateContent(...). The pipe does not currently expose a public setCacheControl(...) builder — CacheControl is intended for direct request construction or custom request pipelines. For higher-level caching ergonomics, prefer setApiMode(ApiMode.Anthropic) and let the Anthropic provider handle prompt caching at the protocol level.
Error Mapping
GenericOpenAIPipe maps transport-level and provider-level failures to the P2PError enum (values: auth, prompt, json, content, transport, context, configuration, none):
| HTTP / condition | Provider error type | P2PError | When |
|---|---|---|---|
| 401 | — | auth | Invalid or missing API key |
| 403 | — | auth | Forbidden (region / model access) |
| 400 | invalid_request_error | prompt | Malformed request body |
| 400 | invalid_api_key | prompt | Provider rejected the key shape |
| 429 | rate_limit_error | transport | Rate limit |
| 5xx | api_error, server_error | transport | Provider server error |
| — | — | transport | Network / socket / request timeout |
| — | — | json | Response body could not be parsed into GenericOpenAIChatResponse |
The mapping is enforced in two places:
- SSE path (
executeStreamingOpenAI) — deserializes the firstdata:payload asGenericOpenAIErrorResponseand classifies onerror.type(authentication_error → auth,rate_limit_error → transport,invalid_request_error/invalid_api_key → prompt,api_error/server_error → transport, default →transport). - Non-streaming path — converts
HttpRequestTimeoutException,SocketTimeoutException, andConnectExceptionintoP2PException(P2PError.transport, ...), and wraps JSON parse failures asP2PException(P2PError.json, ...).
Catch P2PException and inspect errorType for typed handling.
Multimodal Content
GenericOpenAIPipe supports multimodal inputs (images, documents) via MultimodalContent.binaryContent. Binary content is automatically converted to the appropriate format for the selected ApiMode inside generateContent(...):
BinaryContent type | OpenAI mode | Anthropic mode | OpenAI Responses mode |
|---|---|---|---|
Bytes | base64 data URI image block | base64 image block | base64 input item |
Base64String | base64 data URI image block | base64 image block | base64 input item |
CloudReference | URL image block | URL image block | URL input item |
TextDocument | text block | text block | text input item |
Bytes and Base64String are equivalent on the wire — both end up as a data:<mime>;base64,... URL inside an ImageUrlBlock. CloudReference passes the URL through as-is. TextDocument injects the text into the content array as a TextBlock.
Example:
import com.TTT.Pipe.MultimodalContent
import com.TTT.Pipe.BinaryContent
import genericOpenAIPipe.GenericOpenAIPipe
import genericOpenAIPipe.api.ApiMode
import kotlinx.coroutines.runBlocking
import java.io.File
fun main() = runBlocking {
val imageBytes = File("paris.png").readBytes()
val pipe = GenericOpenAIPipe()
.setApiKey(System.getenv("OPENAI_API_KEY"))
.setModel("gpt-4o")
.setApiMode(ApiMode.OpenAI)
.init()
val content = MultimodalContent(
text = "What is in this image?",
binaryContent = mutableListOf(
BinaryContent.Bytes(data = imageBytes, mimeType = "image/png")
)
)
println(pipe.execute(content).text)
}
Next Steps
- Bedrock Mantle Getting Started — Amazon Bedrock Mantle provider guide: endpoint, authentication (Bearer / SigV4 / chunked-streaming), reasoning, streaming, error conditions.
- Bedrock Mantle API Reference —
BedrockMantleConfiguration,BedrockMantleAuth(Bearer / SigV4 / Streaming variants),BedrockMantleEnvenv-var precedence. - Getting Started with GenericOpenAI — Provider recipes, comparison with
OllamaPipe/OpenRouterPipe/BedrockPipe, and a troubleshooting guide. - Pipe Context Protocol — Attach PCP tools to a
GenericOpenAIPipefor sandboxed multi-language tool execution. - Pipe Class API — Core pipe abstraction and base-class builders (
setModel,setTemperature,setMaxTokens,setSystemPrompt,setStopSequences,setSeed,setUser,setLogitBias,setN,setRepetitionPenalty,setPresencePenalty,setContextWindowSize,setTokenBudget). - DistributionGrid — Route tasks across workers with a
GenericOpenAIPiperouter.