What Is PromptCompat in DS2API and How Does It Work?

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.

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

This function, invoked from handlers such as 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) 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. 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.

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 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:

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: NormalizeOpenAIChatRequestBuildOpenAIPromptStandardRequest.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. 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. 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 (entry point and validation), message_normalize.go (role and content transformation), tool_prompt.go (tool description generation and policy handling), prompt_build.go (final assembly and thinking preparation), and standard_request.go (payload construction and downstream interface).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →