Skip to content

feat(openai_compat): implement strict mode compatibility for third-party providers#1683

Open
badgerbees wants to merge 8 commits intosipeed:mainfrom
badgerbees:feature/openai-strict-mode-compatibility
Open

feat(openai_compat): implement strict mode compatibility for third-party providers#1683
badgerbees wants to merge 8 commits intosipeed:mainfrom
badgerbees:feature/openai-strict-mode-compatibility

Conversation

@badgerbees
Copy link
Contributor

@badgerbees badgerbees commented Mar 17, 2026

📝 Description

Summary:

  • Implement OpenAI Strict Mode compatibility for the openai_compat provider.
  • Fail-Safe Sanitization: Automatically strip the strict: true flag from tool definitions for non-native OpenAI providers (Ollama, vLLM, DeepSeek, Groq, etc.).

Changes:

  • pkg/providers/openai_compat/provider.go:
    • Added finalizeTools helper to sanitize tool definitions before wire serialization.
    • Implemented supportsStrictMode to detect native OpenAI/Azure endpoints based on the API base URL.
    • Updated Chat() to utilize lazy tool sanitization.

🗣️ Type of Change

  • 🐞 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 📖 Documentation update
  • ⚡ Code refactoring (no functional changes, no api changes)

🤖 AI Code Generation

  • 🤖 Fully AI-generated (100% AI, 0% Human)
  • 🛠️ Mostly AI-generated (AI draft, Human verified/modified)
  • 👨‍💻 Mostly Human-written (Human lead, AI assisted or none)

🔗 Related Issue

N/A

📚 Technical Context (Skip for Docs)

  • Problem Overview:
    Many "OpenAI-compatible" providers (like Ollama or Groq) strictly reject unknown fields in the tool definition JSON. Specifically, OpenAI's new "strict": true flag for Structured Outputs causes these providers to return 400 Bad Request or 422 Unprocessable Entity errors, crashing tool calls in PicoClaw.

  • Solution Implementation:

    • Environment Detection: The provider now checks if the apiBase is native OpenAI.
    • Selective Sanitization: If the provider is non-native, we strip the strict flag by rebuilding the tool map with only standard fields.
    • Passthrough Optimization: For native OpenAI, the code passes the tool array through as-is, preserving original behavior and avoiding performance overhead.

🧪 Test Environment

  • Hardware: PC
  • OS: Windows / WSL (Ubuntu)
  • Model/Provider: DeepSeek, Ollama, Groq (via openai_compat)
  • Channels: All (Core Provider fix)

📸 Evidence (Optional)

Click to view Logs/Test Results

Test Results:

  • Passed go test ./pkg/providers/openai_compat/...
  • Verified compatibility with non-strict schemas on official OpenAI.
  • Verified removal of incompatible flags on mock non-native endpoints.

☑️ Checklist

  • My code/docs follow the style of this project.
  • I have performed a self-review of my own changes.
  • I have updated the documentation accordingly.

Copilot AI review requested due to automatic review settings March 17, 2026 10:24
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements OpenAI Structured Outputs “strict mode” handling in the openai_compat provider by conditionally sanitizing tool definitions for non-native OpenAI endpoints, and optionally injecting function.strict based on endpoint detection and a caller-provided toggle.

Changes:

  • Route tools through a new finalizeTools helper before JSON serialization.
  • Add supportsStrictMode (currently mirroring the existing OpenAI/Azure detection logic).
  • Add strict-mode sanitization / injection logic with an options["strict_mode"] override.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 123 to 126
if len(tools) > 0 {
requestBody["tools"] = tools
requestBody["tools"] = p.finalizeTools(tools, options)
requestBody["tool_choice"] = "auto"
}
@sipeed-bot sipeed-bot bot added type: enhancement New feature or request domain: provider go Pull requests that update go code labels Mar 17, 2026
Copy link
Collaborator

@yinwm yinwm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Overall: ✅ Ready to merge with minor suggestions


1. Logic Issue ⚠️

strict_mode=true forced on non-native providers may still cause errors

useStrict := isNative
if hasForce {
    useStrict = forceStrict
}

When a user sets strict_mode=true but the provider is Ollama/Groq/DeepSeek (non-native OpenAI), the code will still send strict=true in the request, which will still result in a 400 error.

Suggestion: Either log a warning or ignore the forced setting when the provider doesn't support it:

if hasForce && forceStrict && !isNative {
    // slog.Warn("strict_mode=true ignored for non-OpenAI provider", "provider", p.apiBase)
    useStrict = false
}

2. Potential Edge Case 🔍

The ToolFunctionDefinition struct doesn't have a Strict field. This PR assumes strict is controlled via options, not already present in the input tools.

Question: Could the tools parameter already contain a strict field from upstream? If so, the current sanitization logic won't remove it since it only rebuilds with standard fields.


3. Code Quality 👍

  • ✅ Clear comments explaining the performance optimization path
  • ✅ Good reuse of supportsPromptCacheKey detection logic
  • ✅ Pass-through optimization avoids unnecessary memory allocation

4. Missing Items 📋

Item Status
Unit tests ❌ Missing
Documentation for strict_mode option ❌ Not mentioned
Edge case: forceStrict=true + non-native provider ⚠️ Not handled

5. Suggested Improvement

func (p *Provider) finalizeTools(tools []ToolDefinition, options map[string]any) any {
    forceStrict, hasForce := options["strict_mode"].(bool)
    isNative := supportsStrictMode(p.apiBase)

    // Fast path: native OpenAI without forced setting
    if isNative && !hasForce {
        return tools
    }

    // Decide whether to use strict mode
    useStrict := isNative
    if hasForce {
        if forceStrict && !isNative {
            // Non-native providers don't support strict mode, ignore user setting
            // slog.Warn("strict_mode=true ignored for non-OpenAI provider", "provider", p.apiBase)
            useStrict = false
        } else {
            useStrict = forceStrict
        }
    }

    // ... rest unchanged
}

Conclusion

The core logic is correct and solves a real compatibility issue. Recommend merging after addressing the strict_mode=true edge case (or at least adding a warning log). Unit tests would be a nice addition.

@badgerbees
Copy link
Contributor Author

I've updated the branch to address the review feedback:

  1. Refined Strict Mode Logic: I've implemented the suggested check to prevent 400 errors on non-native providers. If a user tries to force strict_mode: true for a provider like Ollama or Groq, the system now logs a warning and safely falls back to non-strict mode for that request.
  2. Request Sanitization: To address the edge case of field leakage, finalizeTools now rebuilds the tool map from scratch using an explicit field allow-list (name, description, parameters). This effectively 'sanitizes' the output, ensuring no unsupported fields (like a pre-existing strict field from upstream) can reach the final API request.
  3. Unit Tests: Added TestFinalizeTools_StrictMode to verify native detection, automatic stripping for non-native providers, and the behavior of explicit user overrides.

The implementation is now robust against unsupported field errors while maintaining flexible control for native OpenAI endpoints.

@badgerbees badgerbees requested a review from yinwm March 17, 2026 16:17
@yinwm
Copy link
Collaborator

yinwm commented Mar 17, 2026

@badgerbees plz fix PR Linter / tests

@badgerbees
Copy link
Contributor Author

@badgerbees plz fix PR Linter / tests

what the hell there were literally no issues with it when I ran the checks hence the workflow working the first time, I've no idea what these errors meant either + I've just checked again and there were no lint error

let me take a look again since all workflow failed too not just 1

@badgerbees
Copy link
Contributor Author

huh strange all the errors are related to log being undefined yet last workflow it was fine, I guess I'll fix them then

Copilot AI review requested due to automatic review settings March 17, 2026 17:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements OpenAI “Strict Mode” compatibility behavior in the openai_compat provider by conditionally adding/removing the strict tool flag depending on whether the configured endpoint is native OpenAI/Azure vs a third-party OpenAI-compatible API.

Changes:

  • Route tool serialization through a new finalizeTools helper to optionally inject/strip function.strict.
  • Add strict-mode support detection (supportsStrictMode) based on API base hostname (mirrors prompt cache support detection).
  • Add unit coverage for strict tool finalization behavior across native vs non-native API bases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
pkg/providers/openai_compat/provider.go Adds strict-mode detection and tool finalization, and updates Chat() to use it.
pkg/providers/openai_compat/provider_test.go Adds tests covering strict-mode tool finalization behavior and pass-through expectations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

res := p.finalizeTools(tools, tt.options)

if tt.wantSame {
if fmt.Sprintf("%p", res) != fmt.Sprintf("%p", tools) {
@badgerbees
Copy link
Contributor Author

The previous CI failure (undefined: log) was due to an out-of-sync merge with the latest refactor in main, which replaced the standard log package with slog.

I've fixed this by:

  1. Switching to slog: All logging in provider.go (including the new Strict Mode warning) now uses the new structured logging system.
  2. Updating to the common Package: Updated the provider to use the new pkg/providers/common utilities for message serialization and response parsing that were recently merged.
  3. Local Verification: Confirmed that go test ./pkg/providers/openai_compat/... passes locally with the new structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain: provider go Pull requests that update go code type: enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants