# What Is PromptCompat in DS2API and How Does It Work?

> DS2API PromptCompat converts OpenAI chat requests to plain text prompts for LLMs. Learn how it normalizes messages injects tools and formats for any backend.

- Repository: [CJACK./ds2api](https://github.com/CJackHwang/ds2api)
- Tags: deep-dive
- Published: 2026-04-26

---

**PromptCompat is the DS2API subsystem that transforms OpenAI-style chat completion requests into standardized plain-text prompts, handling message normalization, tool injection, and model-specific formatting before forwarding to any LLM backend.**

PromptCompat acts as the compatibility layer within the DS2API open-source project (CJackHwang/ds2api), bridging the gap between OpenAI's API format and the internal prompt format required by various language model backends. This subsystem isolates all OpenAI-specific quirks—including role names, tool schemas, and reasoning blocks—from the core DS2API engine, producing a deterministic flow that converts JSON requests into unified `FinalPrompt` strings compatible with OpenAI, Gemini, Claude, and other providers.

## Request Normalization and Model Resolution

When an HTTP handler receives an incoming chat completion request, it delegates validation and transformation to `NormalizeOpenAIChatRequest` in [`internal/promptcompat/request_normalize.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/request_normalize.go).

```go
stdReq, err := promptcompat.NormalizeOpenAIChatRequest(store, req, traceID)

```

This function, invoked from handlers such as [`internal/httpapi/openai/chat/handler_chat.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/httpapi/openai/chat/handler_chat.go), performs several critical steps:

- **Validates required fields**: Ensures `model` and `messages` exist in the request payload
- **Resolves model aliases**: Uses `config.ResolveModel` to map user-supplied model names to internal configurations
- **Sets capability flags**: Determines default **thinking** and **search** flags based on the resolved model configuration
- **Builds the prompt**: Calls `BuildOpenAIPrompt` to generate the final text and extract tool names

The output is a `StandardRequest` struct (defined in [`internal/promptcompat/standard_request.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/standard_request.go)) that carries the normalized request downstream, including the `FinalPrompt`, `ToolNames`, and metadata flags.

## Message Normalization Pipeline

Before assembly, raw messages undergo transformation via `NormalizeOpenAIMessagesForPrompt` in [`internal/promptcompat/message_normalize.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/message_normalize.go). This function iterates over the `messages` array and normalizes each entry into a slice of `map[string]any` conforming to the internal schema.

The transformation logic handles specific role types:

- **`assistant`**: Processed by `buildAssistantContentForPrompt`, merging normal content with optional reasoning blocks and tool-call history
- **`tool`** or **`function`**: Normalized via `buildToolContentForPrompt` to standardize tool output formatting
- **`user`**, **`system`**, **`developer`**: Roles are lower-cased, with content processed through `NormalizeOpenAIContentForPrompt`
- **Unknown roles**: Fall back to user message treatment

Reasoning blocks receive special formatting, wrapped in labeled delimiters: `[reasoning_content] … [/reasoning_content]`. This enables downstream parsers to identify and handle chain-of-thought content separately from the main response.

## Tool Injection and Choice Policy Logic

When the request contains a `tools` array, PromptCompat injects a system-level tool description into the prompt via `injectToolPrompt` in [`internal/promptcompat/tool_prompt.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/tool_prompt.go).

```go
messages, toolNames = injectToolPrompt(messages, tools, toolPolicy)

```

The injection process generates a description block for each allowed tool:

```

Tool: get_weather
Description: Returns the current weather for a location.
Parameters: {"type":"object","properties":{"city":{"type":"string"}}}

```

This description is prepended to the first `system` message, followed by generic tool-call instructions from `toolcall.BuildToolCallInstructions`.

The **tool-choice policy** (`ToolChoicePolicy`) governs tool invocation behavior through the `parseToolChoicePolicy` function:

- **`auto`**: Allows the model to decide whether to call tools (default behavior)
- **`forced`**: Requires the model to call a specific named tool
- **`required`**: Mandates that the model call *any* available tool
- **`none`**: Prohibits tool calls entirely

The policy validates the `tool_choice` parameter against declared tools and builds an `Allowed` set that restricts which tools appear in the generated prompt.

## Final Prompt Assembly and Payload Construction

The `BuildOpenAIPrompt` function in [`internal/promptcompat/prompt_build.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/prompt_build.go) orchestrates the final assembly:

1. Normalizes messages via `NormalizeOpenAIMessagesForPrompt`
2. Conditionally injects tool prompts based on the policy
3. Invokes `prompt.MessagesPrepareWithThinking(messages, thinkingEnabled)` to prepend a thinking preamble when the thinking flag is active

This function returns the **final prompt string** and a slice of discovered tool names. The `StandardRequest.CompletionPayload` method then packages `FinalPrompt`, `ToolNames`, `Thinking`, and `Stream` flags into the JSON payload sent to the model backend.

## Practical Implementation Example

The following example demonstrates how DS2API processes an OpenAI-formatted request through PromptCompat:

```go
package main

import (
	"fmt"
	"ds2api/internal/promptcompat"
	"ds2api/internal/config"
)

func main() {
	// Simulated incoming OpenAI chat request
	req := map[string]any{
		"model": "gpt-4o",
		"messages": []any{
			map[string]any{"role": "system", "content": "You are a helpful assistant."},
			map[string]any{"role": "user", "content": "What's the weather in Paris?"},
		},
		"tools": []any{
			map[string]any{
				"type": "function",
				"function": map[string]any{
					"name":        "get_weather",
					"description": "Get current weather for a city.",
					"parameters": map[string]any{
						"type": "object",
						"properties": map[string]any{
							"city": map[string]any{"type": "string"},
						},
						"required": []any{"city"},
					},
				},
			},
		},
		"tool_choice": "auto",
		"stream":      true,
	}

	// The config store implements ConfigReader
	var store config.Store

	stdReq, err := promptcompat.NormalizeOpenAIChatRequest(store, req, "trace-123")
	if err != nil {
		panic(err)
	}

	fmt.Println("=== Final Prompt ===")
	fmt.Println(stdReq.FinalPrompt)
	fmt.Println("\nDeclared tools:", stdReq.ToolNames)
	fmt.Println("Thinking enabled:", stdReq.Thinking)
}

```

**Output:**

```

=== Final Prompt ===
You have access to these tools:

Tool: get_weather
Description: Get current weather for a city.
Parameters: {"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}

<tool-call-instructions>

You are a helpful assistant.

What's the weather in Paris?

Declared tools: [get_weather]
Thinking enabled: true

```

## Summary

- **PromptCompat** is the gateway subsystem that converts OpenAI API requests into DS2API's internal format, located in `internal/promptcompat/`
- The normalization flow follows a deterministic path: `NormalizeOpenAIChatRequest` → `BuildOpenAIPrompt` → `StandardRequest.FinalPrompt`
- Message handling supports OpenAI-specific roles (including `developer`) and wraps reasoning content in labeled blocks for downstream parsing
- Tool injection respects configurable policies (`auto`, `forced`, `required`, `none`) and generates plain-text tool descriptions compatible with any text-based LLM
- The architecture decouples API compatibility from model backends, allowing DS2API to support new models by updating configuration rather than rewriting prompt logic

## Frequently Asked Questions

### What is the primary purpose of PromptCompat in DS2API?

PromptCompat serves as the translation layer between OpenAI's chat completions API format and DS2API's internal prompt format. It ensures that messages, tool definitions, and reasoning blocks from OpenAI-style requests are normalized into plain-text prompts that any language model backend can process. This abstraction allows DS2API to support multiple model providers while maintaining a consistent internal interface.

### How does PromptCompat handle tool_choice parameters?

PromptCompat implements four distinct policies via `parseToolChoicePolicy` in [`internal/promptcompat/tool_prompt.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/tool_prompt.go). When `tool_choice` is set to `"auto"`, the model may optionally call tools. `"none"` prohibits tool calls entirely. `"required"` forces the model to select at least one tool, while specific function names force that particular tool. The policy filters the `Allowed` tool set before injection into the system prompt.

### Where does reasoning content get formatted in the PromptCompat pipeline?

Reasoning blocks are formatted within `buildAssistantContentForPrompt` in [`internal/promptcompat/message_normalize.go`](https://github.com/CJackHwang/ds2api/blob/main/internal/promptcompat/message_normalize.go). The function wraps reasoning content in `[reasoning_content]...[/reasoning_content]` delimiters during message normalization. If the `thinking` flag is enabled for the model, `MessagesPrepareWithThinking` (called by `BuildOpenAIPrompt`) may additionally prepend thinking instructions to the final assembled prompt.

### Which source files contain the core PromptCompat logic?

The implementation spans five key files in `internal/promptcompat/`: [`request_normalize.go`](https://github.com/CJackHwang/ds2api/blob/main/request_normalize.go) (entry point and validation), [`message_normalize.go`](https://github.com/CJackHwang/ds2api/blob/main/message_normalize.go) (role and content transformation), [`tool_prompt.go`](https://github.com/CJackHwang/ds2api/blob/main/tool_prompt.go) (tool description generation and policy handling), [`prompt_build.go`](https://github.com/CJackHwang/ds2api/blob/main/prompt_build.go) (final assembly and thinking preparation), and [`standard_request.go`](https://github.com/CJackHwang/ds2api/blob/main/standard_request.go) (payload construction and downstream interface).