Skip to content

feat: add support for openrouter#12878

Merged
SkyeYoung merged 11 commits into
apache:masterfrom
SkyeYoung:young/feat/ai-driver-openrouter
Jan 12, 2026
Merged

feat: add support for openrouter#12878
SkyeYoung merged 11 commits into
apache:masterfrom
SkyeYoung:young/feat/ai-driver-openrouter

Conversation

@SkyeYoung

@SkyeYoung SkyeYoung commented Jan 9, 2026

Copy link
Copy Markdown
Member

Description

Refer to the changes in #12565 to add support for openrouter

Already tested in real world:

young@ubuntu-20:~/apisix$ apisix restart
/usr/local/openresty//luajit/bin/luajit ./apisix/cli/apisix.lua restart
configuration test is successful
nginx: [warn] [lua] config_yaml.lua:198: read_apisix_config(): config file /home/young/apisix/conf/apisix.yaml reloaded.
young@ubuntu-20:~/apisix$ curl -X POST http://localhost:9080/anything \
>   -H "Content-Type: application/json" \
>   -d '{"messages": [{"role": "user", "content": "Hello"}]}'
data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":"!"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":" How"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":" can"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":" I"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":" assist"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":" you"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":" today"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":"?"},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":"stop","native_finish_reason":"stop","logprobs":null}],"system_fingerprint":"fp_deacdd5f6f"}

data: {"id":"gen-1767948065-l9Xfdy3Lv0vOkdZq5gxE","provider":"OpenAI","model":"openai/gpt-4o","object":"chat.completion.chunk","created":1767948065,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null,"native_finish_reason":null,"logprobs":null}],"usage":{"prompt_tokens":8,"completion_tokens":9,"total_tokens":17,"cost":0.00011,"is_byok":false,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0,"video_tokens":0},"cost_details":{"upstream_inference_cost":null,"upstream_inference_prompt_cost":0.00002,"upstream_inference_completions_cost":0.00009},"completion_tokens_details":{"reasoning_tokens":0,"image_tokens":0}}}

data: [DONE]

Which issue(s) this PR fixes:

Fixes #

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)

@SkyeYoung
SkyeYoung marked this pull request as ready for review January 9, 2026 08:44
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jan 9, 2026
@moonming
moonming requested review from Copilot and removed request for bzp2010, kayx23 and moonming January 11, 2026 07:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds support for the OpenRouter AI provider to APISIX's AI proxy plugins. OpenRouter is added as a new provider option alongside existing providers like OpenAI, DeepSeek, Azure, and AIMLAPI. The implementation follows the established pattern for OpenAI-compatible providers.

Changes:

  • Adds OpenRouter driver using the OpenAI-compatible base driver with endpoint https://openrouter.ai/api/v1/chat/completions
  • Updates plugin schemas to include "openrouter" as a valid provider option
  • Updates both English and Chinese documentation to reflect the new provider
  • Adds comprehensive test coverage for the OpenRouter integration

Reviewed changes

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

Show a summary per file
File Description
t/plugin/ai-proxy-openrouter.t Adds test suite for OpenRouter provider with authentication, streaming, and basic functionality tests
apisix/plugins/ai-drivers/openrouter.lua New driver implementation for OpenRouter using OpenAI-compatible base
apisix/plugins/ai-drivers/schema.lua Updates schema to include OpenRouter in provider lists and compatibility checks
apisix/plugins/ai-proxy/schema.lua Adds "openrouter" to valid provider enums in both multi and single instance schemas
apisix/plugins/ai-request-rewrite.lua Adds "openrouter" to the provider enum for AI request rewriting
docs/en/latest/plugins/ai-request-rewrite.md Documents OpenRouter as available provider option (with typo fix needed)
docs/en/latest/plugins/ai-proxy.md Documents OpenRouter provider with endpoint details (with typo fix needed)
docs/en/latest/plugins/ai-proxy-multi.md Documents OpenRouter in multi-instance plugin configuration (with typo fix needed)
docs/zh/latest/plugins/ai-request-rewrite.md Chinese translation with OpenRouter documentation (with typo fix needed)
docs/zh/latest/plugins/ai-proxy.md Chinese translation with OpenRouter documentation
docs/zh/latest/plugins/ai-proxy-multi.md Chinese translation with OpenRouter documentation

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

"stream": true
},
"override": {
"endpoint": "http://localhost:7737/v1/chat/completions"

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

The endpoint port in TEST 3 is set to 7737, but the mock server in the http_config listens on port 6724. This mismatch will cause the test to fail as it won't be able to connect to the mock server.

Suggested change
"endpoint": "http://localhost:7737/v1/chat/completions"
"endpoint": "http://localhost:6724/v1/chat/completions"

Copilot uses AI. Check for mistakes.
| ------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------ |
| prompt | Yes | String | The prompt send to LLM service. |
| provider | Yes | String | Name of the LLM service. Available options: openai, deekseek, azure-openai, aimlapi and openai-compatible. When `aimlapi` is selected, the plugin uses the OpenAI-compatible driver with a default endpoint of `https://api.aimlapi.com/v1/chat/completions`. |
| provider | Yes | String | Name of the LLM service. Available options: openai, deekseek, azure-openai, aimlapi, openrouter and openai-compatible. When `aimlapi` is selected, the plugin uses the OpenAI-compatible driver with a default endpoint of `https://api.aimlapi.com/v1/chat/completions`. |

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

There is a typo in "deekseek" - it should be "deepseek" for consistency with the actual provider name used throughout the codebase.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This should be fixed in another pr.

| ------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------ |
| prompt | 是 | String | 发送到 LLM 服务的提示。 |
| provider | 是 | String | LLM 服务的名称。可用选项:openai、deekseek、azure-openai、aimlapi 和 openai-compatible。当选择 `aimlapi` 时,插件使用 OpenAI 兼容驱动程序,默认端点为 `https://api.aimlapi.com/v1/chat/completions`。 |
| provider | 是 | String | LLM 服务的名称。可用选项:openai、deekseek、azure-openai、aimlapi、openrouter 和 openai-compatible。当选择 `aimlapi` 时,插件使用 OpenAI 兼容驱动程序,默认端点为 `https://api.aimlapi.com/v1/chat/completions`。 |

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

There is a typo in "deekseek" - it should be "deepseek" for consistency with the actual provider name used throughout the codebase.

Copilot uses AI. Check for mistakes.
| instances | array[object] | True | | | LLM instance configurations. |
| instances.name | string | True | | | Name of the LLM service instance. |
| instances.provider | string | True | | [openai, deepseek, azure-openai, aimlapi, openai-compatible] | LLM service provider. When set to `openai`, the Plugin will proxy the request to `api.openai.com`. When set to `deepseek`, the Plugin will proxy the request to `api.deepseek.com`. When set to `aimlapi`, the Plugin uses the OpenAI-compatible driver and proxies the request to `api.aimlapi.com` by default. When set to `openai-compatible`, the Plugin will proxy the request to the custom endpoint configured in `override`. |
| instances.provider | string | True | | [openai, deepseek, azure-openai, aimlapi, openrouter, openai-compatible] | LLM service provider. When set to `openai`, the Plugin will proxy the request to `api.openai.com`. When set to `deepseek`, the Plugin will proxy the request to `api.deepseek.com`. When set to `aimlapi`, the Plugin uses the OpenAI-compatible driver and proxies the request to `api.aimlapi.com` by default. When set to `openrouter`, the Plugin uses the OpenAI-compatible driver and proxies the request to `openrouter.ai` by default. When set to `openai-compatible`, the Plugin will proxy the request to the custom endpoint configured in `override`. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The azure-openai PR didn't add description for the option - consider adding it here? I think it does something like this

When set to azure-openai, the plugin proxies requests to the custom endpoint configured in override and additionally removes the model parameter from user requests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This should be handled in a separate PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

(when I have time. Or I will create a issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ok

@SkyeYoung
SkyeYoung requested a review from kayx23 January 12, 2026 02:43
Comment thread apisix/plugins/ai-drivers/schema.lua Outdated
["openai-compatible"] = openai_compatible_chat_schema,
["azure-openai"] = openai_compatible_chat_schema
["azure-openai"] = openai_compatible_chat_schema,
["openrouter"] = openai_compatible_chat_schema

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we can add , at the end of this line

Comment thread apisix/plugins/ai-drivers/schema.lua Outdated
}

function _M.is_openai_compatible_provider(provider)
if provider == "openai" or

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can use an array object to maintain it

It is easy to maintain in this way, here is the example code:

do
    local openai_compatible_list = {
        "openai",
        "azure_openai",
        ... ...
        "together",
    }
    local openai_compatible_kv = {}
    for _, provider in ipairs(openai_compatible_list) do
        openai_compatible_kv[provider] = true
    end

    function _M.is_openai_compatible_provider(provider)
        if openai_compatible_kv[provider] then
            return true
        end

        ...
    end

end

Comment thread apisix/plugins/ai-proxy/schema.lua Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

try to reuse the provider array list

Image

we can put the array list in schema.lua

Comment thread apisix/plugins/ai-proxy/schema.lua Outdated
"aimlapi",
"openai-compatible",
"azure-openai"
"azure-openai",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ditto

@SkyeYoung
SkyeYoung requested a review from membphis January 12, 2026 06:41

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@membphis membphis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@bzp2010 bzp2010 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code modification approved

| ------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------ |
| prompt | Yes | String | The prompt send to LLM service. |
| provider | Yes | String | Name of the LLM service. Available options: openai, deekseek, azure-openai, aimlapi and openai-compatible. When `aimlapi` is selected, the plugin uses the OpenAI-compatible driver with a default endpoint of `https://api.aimlapi.com/v1/chat/completions`. |
| provider | Yes | String | Name of the LLM service. Available options: openai, deekseek, azure-openai, aimlapi, openrouter and openai-compatible. When `aimlapi` is selected, the plugin uses the OpenAI-compatible driver with a default endpoint of `https://api.aimlapi.com/v1/chat/completions`. |

@kayx23 kayx23 Jan 12, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The provider description for ai-request-rewrite is not great - it only explained aimlapi. This should be noted and improved (maybe in a different PR?) cc: @Yilialinn

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes.

@kayx23 kayx23 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Other doc changes ok

@SkyeYoung
SkyeYoung merged commit 9f3716d into apache:master Jan 12, 2026
52 of 56 checks passed
@SkyeYoung
SkyeYoung deleted the young/feat/ai-driver-openrouter branch January 12, 2026 12:08
wistefan pushed a commit to wistefan/apisix that referenced this pull request Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants