Skip to content

Pass --openai-api-base-path / --anthropic-api-base-path to AWF when URL contains path #23046

@lpcox

Description

@lpcox

Summary

When OPENAI_BASE_URL or ANTHROPIC_BASE_URL in engine.env contains a path prefix (e.g., https://host.com/serving-endpoints), the compiler strips the path and only passes the hostname via --openai-api-target. This causes API requests to go to https://host/v1/chat/completions instead of https://host/serving-endpoints/v1/chat/completions, returning 404.

The AWF firewall already supports --openai-api-base-path and --anthropic-api-base-path flags (added in a recent release). The compiler just needs to extract the path component and pass it.

Upstream tracking: /issues/21320#issuecomment-4131507565

Impact

Any OPENAI_BASE_URL or ANTHROPIC_BASE_URL with a required path prefix silently fails:

  • Databricks serving endpoints (/serving-endpoints)
  • Azure OpenAI deployments (/openai/deployments/<name>)
  • Corporate LLM routers with path-based routing

Root Cause

extractAPITargetHost() in pkg/workflow/awf_helpers.go:344-347 intentionally strips the path:

// Remove path suffix if present (everything after first /)
if idx := strings.Index(host, "/"); idx != -1 {
    host = host[:idx]
}

This only returns the hostname for --openai-api-target. The path component is discarded and never passed to AWF.

Solution

1. Add extractAPIBasePath() function (pkg/workflow/awf_helpers.go)

Add a new function that extracts the path component from the URL:

// extractAPIBasePath extracts the path component from a custom API base URL in engine.env.
// Returns the path prefix (e.g., "/serving-endpoints") or empty string if no path is present.
func extractAPIBasePath(workflowData *WorkflowData, envVar string) string {
    if workflowData == nil || workflowData.EngineConfig == nil || workflowData.EngineConfig.Env == nil {
        return ""
    }

    baseURL, exists := workflowData.EngineConfig.Env[envVar]
    if !exists || baseURL == "" {
        return ""
    }

    // Remove protocol prefix
    host := baseURL
    if idx := strings.Index(host, "://"); idx != -1 {
        host = host[idx+3:]
    }

    // Extract path (everything after first /)
    if idx := strings.Index(host, "/"); idx != -1 {
        path := host[idx:] // e.g., "/serving-endpoints"
        // Remove trailing slash if present
        path = strings.TrimRight(path, "/")
        if path != "" {
            return path
        }
    }

    return ""
}

2. Pass base path flags in BuildAWFArgs() (pkg/workflow/awf_helpers.go)

After the existing --openai-api-target and --anthropic-api-target lines (~L213-223), add:

// Pass base path if URL contains a path component
openaiBasePath := extractAPIBasePath(config.WorkflowData, "OPENAI_BASE_URL")
if openaiBasePath != "" {
    awfArgs = append(awfArgs, "--openai-api-base-path", openaiBasePath)
    awfHelpersLog.Printf("Added --openai-api-base-path=%s", openaiBasePath)
}

anthropicBasePath := extractAPIBasePath(config.WorkflowData, "ANTHROPIC_BASE_URL")
if anthropicBasePath != "" {
    awfArgs = append(awfArgs, "--anthropic-api-base-path", anthropicBasePath)
    awfHelpersLog.Printf("Added --anthropic-api-base-path=%s", anthropicBasePath)
}

3. Add tests (pkg/workflow/awf_helpers_test.go)

Add tests for extractAPIBasePath:

func TestExtractAPIBasePath(t *testing.T) {
    tests := []struct {
        name     string
        url      string
        expected string
    }{
        {"databricks serving endpoint", "https://host.com/serving-endpoints", "/serving-endpoints"},
        {"azure openai deployment", "https://host.com/openai/deployments/gpt-4", "/openai/deployments/gpt-4"},
        {"simple path", "https://host.com/v1", "/v1"},
        {"trailing slash stripped", "https://host.com/api/", "/api"},
        {"no path", "https://host.com", ""},
        {"bare hostname", "host.com", ""},
        {"root path only", "https://host.com/", ""},
    }
    // ... table-driven test body
}

Add integration test for BuildAWFArgs:

t.Run("includes openai-api-base-path when URL has path component", func(t *testing.T) {
    // Set OPENAI_BASE_URL to "https://host.com/serving-endpoints"
    // Verify args contain both --openai-api-target and --openai-api-base-path
})

4. Recompile workflows

make build && make recompile && make agent-finish

No new frontmatter needed

This fix uses the existing engine.env frontmatter. Workflow authors already configure custom endpoints via env vars:

engine:
  id: codex
  env:
    OPENAI_BASE_URL: "https://stone-dataplatform-production.cloud.databricks.com/serving-endpoints"
    OPENAI_API_KEY: ${{ secrets.DATABRICKS_KEY }}

The compiler will automatically extract and pass the path component — no new frontmatter fields required.

Checklist

  • Add extractAPIBasePath() function to awf_helpers.go
  • Call it in BuildAWFArgs() for both OpenAI and Anthropic URLs
  • Add unit tests for extractAPIBasePath()
  • Add integration test for BuildAWFArgs with path-containing URLs
  • make build && make recompile && make agent-finish

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions