Copilot commented on code in PR #13229:
URL: https://github.com/apache/apisix/pull/13229#discussion_r3090654864


##########
t/plugin/ai-proxy-protocol-conversion.t:
##########
@@ -1382,3 +1382,152 @@ OK: usage-only chunk produced message_delta with usage
 type: done
 --- error_log
 Anthropic SSE error: type=overloaded_error, message=Overloaded
+
+
+
+=== TEST 27: Set up route for response format mismatch test – 
openai-compatible provider with Anthropic override endpoint
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "uri": "/v1/messages",
+                    "plugins": {
+                        "ai-proxy": {
+                            "provider": "openai-compatible",
+                            "auth": {
+                                "header": {
+                                    "Authorization": "Bearer token"
+                                }
+                            },
+                            "options": {
+                                "model": "test-model"
+                            },
+                            "override": {
+                                "endpoint": "http://localhost:6730/v1/messages";
+                            }
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 28: Streaming 502 when converter receives mismatched upstream 
response format
+When the client sends Anthropic format (detected via /v1/messages URI) but the 
provider
+is openai-compatible (only supports openai-chat), a converter bridges the gap. 
If the
+upstream endpoint also returns Anthropic-format SSE (instead of OpenAI), the 
converter
+cannot parse any events and the gateway should return 502 instead of crashing.
+--- http_config
+        server {
+            server_name anthropic_upstream;
+            listen 6730;
+
+            default_type 'application/json';
+
+            location /v1/messages {
+                content_by_lua_block {
+                    local json = require("toolkit.json")
+                    ngx.header["Content-Type"] = "text/event-stream"
+
+                    -- Return Anthropic-format SSE events (not OpenAI format)
+                    ngx.say("event: message_start")
+                    ngx.say("data: " .. json.encode({
+                        type = "message_start",
+                        message = {
+                            id = "msg_123",
+                            type = "message",
+                            role = "assistant",
+                            model = "test-model",
+                            content = {},
+                            usage = { input_tokens = 10, output_tokens = 0 },
+                        }
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: content_block_start")
+                    ngx.say("data: " .. json.encode({
+                        type = "content_block_start",
+                        index = 0,
+                        content_block = { type = "text", text = "" },
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: content_block_delta")
+                    ngx.say("data: " .. json.encode({
+                        type = "content_block_delta",
+                        index = 0,
+                        delta = { type = "text_delta", text = "Hello" },
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: content_block_stop")
+                    ngx.say("data: " .. json.encode({
+                        type = "content_block_stop",
+                        index = 0,
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: message_delta")
+                    ngx.say("data: " .. json.encode({
+                        type = "message_delta",
+                        delta = { stop_reason = "end_turn" },
+                        usage = { output_tokens = 5 },
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: message_stop")
+                    ngx.say("data: {}")
+                    ngx.say("")
+                }
+            }
+        }
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require("resty.http")
+            local httpc = http.new()
+
+            local ok, err = httpc:connect({
+                scheme = "http",
+                host = "localhost",
+                port = ngx.var.server_port,
+            })
+
+            local res, err = httpc:request({
+                method = "POST",
+                path = "/v1/messages",
+                headers = { ["Content-Type"] = "application/json", 
["Connection"] = "close" },
+                body = [[{
+                    "model": "test-model",
+                    "messages": [{"role": "user", "content": "Hi"}],
+                    "stream": true
+                }]],
+            })
+
+            ngx.status = res.status
+            local body = res:read_body()

Review Comment:
   `local body = res:read_body()` is assigned but never used in this test 
block. Consider removing it to keep the test focused, or use it to assert the 
502 response body/error message if that’s important for this scenario.
   ```suggestion
               res:read_body()
   ```



##########
apisix/plugins/ai-providers/base.lua:
##########
@@ -292,6 +314,19 @@ function _M.parse_streaming_response(self, ctx, res, 
target_proto, converter)
                     end
                 end
             end

Review Comment:
   In the EOF buffered-remainder path, parsed events are converted and usage is 
merged, but other side effects handled in the main loop are missing (e.g. 
appending `parsed.texts` into `contents` / 
`ctx.llm_response_contents_in_chunk`, and setting `ctx.var.llm_request_done` 
when `parsed.type` is `done`/`usage_and_done`). This can leave 
`llm_response_text`/stream moderation state incomplete when the final SSE event 
is only completed at EOF. Consider reusing the same per-event handling logic 
here as in the main loop before emitting `eof_converted_chunks`.



##########
t/plugin/ai-proxy-protocol-conversion.t:
##########
@@ -1382,3 +1382,152 @@ OK: usage-only chunk produced message_delta with usage
 type: done
 --- error_log
 Anthropic SSE error: type=overloaded_error, message=Overloaded
+
+
+
+=== TEST 27: Set up route for response format mismatch test – 
openai-compatible provider with Anthropic override endpoint
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "uri": "/v1/messages",
+                    "plugins": {
+                        "ai-proxy": {
+                            "provider": "openai-compatible",
+                            "auth": {
+                                "header": {
+                                    "Authorization": "Bearer token"
+                                }
+                            },
+                            "options": {
+                                "model": "test-model"
+                            },
+                            "override": {
+                                "endpoint": "http://localhost:6730/v1/messages";
+                            }
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 28: Streaming 502 when converter receives mismatched upstream 
response format
+When the client sends Anthropic format (detected via /v1/messages URI) but the 
provider
+is openai-compatible (only supports openai-chat), a converter bridges the gap. 
If the
+upstream endpoint also returns Anthropic-format SSE (instead of OpenAI), the 
converter
+cannot parse any events and the gateway should return 502 instead of crashing.
+--- http_config
+        server {
+            server_name anthropic_upstream;
+            listen 6730;
+
+            default_type 'application/json';
+
+            location /v1/messages {
+                content_by_lua_block {
+                    local json = require("toolkit.json")
+                    ngx.header["Content-Type"] = "text/event-stream"
+
+                    -- Return Anthropic-format SSE events (not OpenAI format)
+                    ngx.say("event: message_start")
+                    ngx.say("data: " .. json.encode({
+                        type = "message_start",
+                        message = {
+                            id = "msg_123",
+                            type = "message",
+                            role = "assistant",
+                            model = "test-model",
+                            content = {},
+                            usage = { input_tokens = 10, output_tokens = 0 },
+                        }
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: content_block_start")
+                    ngx.say("data: " .. json.encode({
+                        type = "content_block_start",
+                        index = 0,
+                        content_block = { type = "text", text = "" },
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: content_block_delta")
+                    ngx.say("data: " .. json.encode({
+                        type = "content_block_delta",
+                        index = 0,
+                        delta = { type = "text_delta", text = "Hello" },
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: content_block_stop")
+                    ngx.say("data: " .. json.encode({
+                        type = "content_block_stop",
+                        index = 0,
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: message_delta")
+                    ngx.say("data: " .. json.encode({
+                        type = "message_delta",
+                        delta = { stop_reason = "end_turn" },
+                        usage = { output_tokens = 5 },
+                    }))
+                    ngx.say("")
+                    ngx.flush(true)
+
+                    ngx.say("event: message_stop")
+                    ngx.say("data: {}")
+                    ngx.say("")
+                }
+            }
+        }
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require("resty.http")
+            local httpc = http.new()
+
+            local ok, err = httpc:connect({
+                scheme = "http",
+                host = "localhost",
+                port = ngx.var.server_port,
+            })
+
+            local res, err = httpc:request({
+                method = "POST",
+                path = "/v1/messages",
+                headers = { ["Content-Type"] = "application/json", 
["Connection"] = "close" },
+                body = [[{
+                    "model": "test-model",
+                    "messages": [{"role": "user", "content": "Hi"}],
+                    "stream": true
+                }]],
+            })
+
+            ngx.status = res.status
+            local body = res:read_body()
+            ngx.say("status: " .. res.status)
+        }
+    }
+--- error_code: 200

Review Comment:
   This test sets `ngx.status = res.status` (502) but still declares `--- 
error_code: 200`. As written, `/t` will respond with HTTP 502 and the harness 
will likely fail. Either update `--- error_code` to 502, or avoid overriding 
`ngx.status` and only assert on the printed body.
   ```suggestion
   --- error_code: 502
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to