Files
LocalAI/core/http/openresponses_test.go
Ettore Di Giacinto 3387bfaee0 feat(api): add support for open responses specification (#8063)
* feat: openresponses

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Add ttl settings, fix tests

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix: register cors middleware by default

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* satisfy schema

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Logitbias and logprobs

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Add grammar

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* SSE compliance

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* tool JSON conversion

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* support background mode

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* swagger

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* drop code. This is handled in the handler

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Small refactorings

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* background mode for MCP

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-01-17 22:11:47 +01:00

1028 lines
31 KiB
Go

package http_test
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/labstack/echo/v4"
"github.com/mudler/LocalAI/core/application"
"github.com/mudler/LocalAI/core/config"
. "github.com/mudler/LocalAI/core/http"
"github.com/mudler/LocalAI/pkg/system"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/mudler/xlog"
)
const testModel = "Qwen3-VL-2B-Instruct-GGUF"
var _ = Describe("Open Responses API", func() {
var app *echo.Echo
var c context.Context
var cancel context.CancelFunc
commonOpts := []config.AppOption{
config.WithDebug(true),
}
Context("API with ephemeral models", func() {
BeforeEach(func(sc SpecContext) {
var err error
backendPath := os.Getenv("BACKENDS_PATH")
c, cancel = context.WithCancel(context.Background())
systemState, err := system.GetSystemState(
system.WithBackendPath(backendPath),
system.WithModelPath(modelDir),
)
Expect(err).ToNot(HaveOccurred())
application, err := application.New(
append(commonOpts,
config.WithContext(c),
config.WithSystemState(systemState),
config.WithApiKeys([]string{apiKey}),
config.WithModelsURL("https://huggingface.co/unsloth/Qwen3-VL-2B-Instruct-GGUF"),
)...)
Expect(err).ToNot(HaveOccurred())
app, err = API(application)
Expect(err).ToNot(HaveOccurred())
go func() {
if err := app.Start("127.0.0.1:9090"); err != nil && err != http.ErrServerClosed {
xlog.Error("server error", "error", err)
}
}()
// Wait for API to be ready
Eventually(func() error {
resp, err := http.Get("http://127.0.0.1:9090/healthz")
if err != nil {
return err
}
resp.Body.Close()
return nil
}, "2m").ShouldNot(HaveOccurred())
})
AfterEach(func(sc SpecContext) {
cancel()
if app != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := app.Shutdown(ctx)
Expect(err).ToNot(HaveOccurred())
}
})
Context("HTTP Protocol Compliance", func() {
It("MUST accept application/json Content-Type", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
// Should accept the request (may fail on model not found, but should accept Content-Type)
Expect(resp.StatusCode).To(Or(Equal(200), Equal(400), Equal(500)))
})
It("MUST return application/json for non-streaming responses", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
"stream": false,
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
contentType := resp.Header.Get("Content-Type")
if resp.StatusCode == 200 {
Expect(contentType).To(ContainSubstring("application/json"))
}
})
It("MUST return text/event-stream for streaming responses", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
"stream": true,
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
contentType := resp.Header.Get("Content-Type")
if resp.StatusCode == 200 {
Expect(contentType).To(Equal("text/event-stream"))
}
})
It("MUST end streaming with [DONE] terminal event", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
"stream": true,
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
body, err := io.ReadAll(resp.Body)
Expect(err).ToNot(HaveOccurred())
bodyStr := string(body)
// Should end with [DONE]
Expect(bodyStr).To(ContainSubstring("data: [DONE]"))
}
})
It("MUST have event field matching type in body", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
"stream": true,
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
body, err := io.ReadAll(resp.Body)
Expect(err).ToNot(HaveOccurred())
bodyStr := string(body)
// Parse SSE events
lines := strings.Split(bodyStr, "\n")
for i, line := range lines {
if strings.HasPrefix(line, "event: ") {
eventType := strings.TrimPrefix(line, "event: ")
// Next line should be data: with matching type
if i+1 < len(lines) && strings.HasPrefix(lines[i+1], "data: ") {
dataLine := strings.TrimPrefix(lines[i+1], "data: ")
var eventData map[string]interface{}
if err := json.Unmarshal([]byte(dataLine), &eventData); err == nil {
if typeVal, ok := eventData["type"].(string); ok {
Expect(typeVal).To(Equal(eventType))
}
}
}
}
}
}
})
})
Context("Response Structure", func() {
It("MUST return id field", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response).To(HaveKey("id"))
Expect(response["id"]).ToNot(BeEmpty())
}
})
It("MUST return object field as 'response'", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response).To(HaveKey("object"))
Expect(response["object"]).To(Equal("response"))
}
})
It("MUST return created_at timestamp", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response).To(HaveKey("created_at"))
// Should be a number (unix timestamp)
createdAt, ok := response["created_at"].(float64)
Expect(ok).To(BeTrue())
Expect(createdAt).To(BeNumerically(">", 0))
}
})
It("MUST return status field", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response).To(HaveKey("status"))
status, ok := response["status"].(string)
Expect(ok).To(BeTrue())
Expect(status).To(BeElementOf("in_progress", "completed", "failed", "incomplete"))
}
})
It("MUST return model field", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response).To(HaveKey("model"))
Expect(response["model"]).ToNot(BeEmpty())
}
})
It("MUST return output array of items", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response).To(HaveKey("output"))
output, ok := response["output"].([]interface{})
Expect(ok).To(BeTrue())
Expect(output).ToNot(BeNil())
}
})
})
Context("Items", func() {
It("MUST include id field on all items", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
output, ok := response["output"].([]interface{})
if ok {
for _, item := range output {
itemMap, ok := item.(map[string]interface{})
Expect(ok).To(BeTrue())
Expect(itemMap).To(HaveKey("id"))
Expect(itemMap["id"]).ToNot(BeEmpty())
}
}
}
})
It("MUST include type field on all items", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
output, ok := response["output"].([]interface{})
if ok {
for _, item := range output {
itemMap, ok := item.(map[string]interface{})
Expect(ok).To(BeTrue())
Expect(itemMap).To(HaveKey("type"))
Expect(itemMap["type"]).ToNot(BeEmpty())
}
}
}
})
It("MUST include status field on all items", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
output, ok := response["output"].([]interface{})
if ok {
for _, item := range output {
itemMap, ok := item.(map[string]interface{})
Expect(ok).To(BeTrue())
Expect(itemMap).To(HaveKey("status"))
status, ok := itemMap["status"].(string)
Expect(ok).To(BeTrue())
Expect(status).To(BeElementOf("in_progress", "completed", "incomplete"))
}
}
}
})
It("MUST support message items with role field", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": []map[string]interface{}{
{
"type": "message",
"role": "user",
"content": []map[string]interface{}{
{
"type": "input_text",
"text": "Hello",
},
},
},
},
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
output, ok := response["output"].([]interface{})
if ok && len(output) > 0 {
itemMap, ok := output[0].(map[string]interface{})
Expect(ok).To(BeTrue())
if itemMap["type"] == "message" {
Expect(itemMap).To(HaveKey("role"))
role, ok := itemMap["role"].(string)
Expect(ok).To(BeTrue())
Expect(role).To(BeElementOf("user", "assistant", "system", "developer"))
}
}
}
})
})
Context("Content Types", func() {
It("MUST support input_text content", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": []map[string]interface{}{
{
"type": "message",
"role": "user",
"content": []map[string]interface{}{
{
"type": "input_text",
"text": "Hello world",
},
},
},
},
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
// Should accept the request
Expect(resp.StatusCode).To(Or(Equal(200), Equal(400), Equal(500)))
})
It("MUST support input_image content with URL", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": []map[string]interface{}{
{
"type": "message",
"role": "user",
"content": []map[string]interface{}{
{
"type": "input_image",
"image_url": "https://example.com/image.png",
"detail": "auto",
},
},
},
},
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
// Should accept the request
Expect(resp.StatusCode).To(Or(Equal(200), Equal(400), Equal(500)))
})
It("MUST support input_image content with base64", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": []map[string]interface{}{
{
"type": "message",
"role": "user",
"content": []map[string]interface{}{
{
"type": "input_image",
"image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"detail": "auto",
},
},
},
},
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
// Should accept the request
Expect(resp.StatusCode).To(Or(Equal(200), Equal(400), Equal(500)))
})
It("MUST support output_text content", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
var response map[string]interface{}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &response)
Expect(err).ToNot(HaveOccurred())
output, ok := response["output"].([]interface{})
if ok && len(output) > 0 {
itemMap, ok := output[0].(map[string]interface{})
Expect(ok).To(BeTrue())
if itemMap["type"] == "message" {
content, ok := itemMap["content"].([]interface{})
if ok && len(content) > 0 {
contentMap, ok := content[0].(map[string]interface{})
if ok {
contentType, _ := contentMap["type"].(string)
if contentType == "output_text" {
Expect(contentMap).To(HaveKey("text"))
}
}
}
}
}
}
})
})
Context("Streaming Events", func() {
It("MUST emit response.created as first event", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
"stream": true,
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
body, err := io.ReadAll(resp.Body)
Expect(err).ToNot(HaveOccurred())
bodyStr := string(body)
// Should contain response.created event
Expect(bodyStr).To(ContainSubstring("response.created"))
}
})
It("MUST include sequence_number in all events", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Hello",
"stream": true,
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode == 200 {
body, err := io.ReadAll(resp.Body)
Expect(err).ToNot(HaveOccurred())
bodyStr := string(body)
// Parse SSE events and check for sequence_number
lines := strings.Split(bodyStr, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "data: ") {
dataLine := strings.TrimPrefix(line, "data: ")
if dataLine != "[DONE]" {
var eventData map[string]interface{}
if err := json.Unmarshal([]byte(dataLine), &eventData); err == nil {
if _, hasType := eventData["type"]; hasType {
Expect(eventData).To(HaveKey("sequence_number"))
}
}
}
}
}
}
})
})
Context("Error Handling", func() {
It("MUST return structured error with type and message fields", func() {
reqBody := map[string]interface{}{
"model": "nonexistent-model",
"input": "Hello",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
if resp.StatusCode >= 400 {
var errorResp map[string]interface{}
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &errorResp)
if errorResp["error"] != nil {
errorObj, ok := errorResp["error"].(map[string]interface{})
if ok {
Expect(errorObj).To(HaveKey("type"))
Expect(errorObj).To(HaveKey("message"))
}
}
}
})
})
Context("Previous Response ID", func() {
It("should load previous response and concatenate context", func() {
// First, create a response
reqBody1 := map[string]interface{}{
"model": testModel,
"input": "What is 2+2?",
}
payload1, err := json.Marshal(reqBody1)
Expect(err).ToNot(HaveOccurred())
req1, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload1))
Expect(err).ToNot(HaveOccurred())
req1.Header.Set("Content-Type", "application/json")
req1.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp1, err := client.Do(req1)
Expect(err).ToNot(HaveOccurred())
defer resp1.Body.Close()
// Check if first response succeeded
if resp1.StatusCode != 200 {
Skip("First response failed, skipping previous_response_id test (backend may not be available)")
}
var response1 map[string]interface{}
body1, err := io.ReadAll(resp1.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body1, &response1)
Expect(err).ToNot(HaveOccurred())
responseID, ok := response1["id"].(string)
Expect(ok).To(BeTrue())
Expect(responseID).ToNot(BeEmpty())
// Now create a new response with previous_response_id
reqBody2 := map[string]interface{}{
"model": testModel,
"input": "What about 3+3?",
"previous_response_id": responseID,
}
payload2, err := json.Marshal(reqBody2)
Expect(err).ToNot(HaveOccurred())
req2, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload2))
Expect(err).ToNot(HaveOccurred())
req2.Header.Set("Content-Type", "application/json")
req2.Header.Set("Authorization", bearerKey)
resp2, err := client.Do(req2)
Expect(err).ToNot(HaveOccurred())
defer resp2.Body.Close()
var response2 map[string]interface{}
body2, err := io.ReadAll(resp2.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body2, &response2)
Expect(err).ToNot(HaveOccurred())
Expect(response2["previous_response_id"]).To(Equal(responseID))
Expect(response2["status"]).To(Equal("completed"))
})
It("should return error for invalid previous_response_id", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": "Test",
"previous_response_id": "nonexistent_response_id",
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(404))
var errorResp map[string]interface{}
body, _ := io.ReadAll(resp.Body)
json.Unmarshal(body, &errorResp)
if errorResp["error"] != nil {
errorObj, ok := errorResp["error"].(map[string]interface{})
if ok {
Expect(errorObj["type"]).To(Equal("not_found"))
Expect(errorObj["param"]).To(Equal("previous_response_id"))
}
}
})
})
Context("Item Reference", func() {
It("should resolve item_reference in input", func() {
// First, create a response with items
reqBody1 := map[string]interface{}{
"model": testModel,
"input": "Hello",
}
payload1, err := json.Marshal(reqBody1)
Expect(err).ToNot(HaveOccurred())
req1, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload1))
Expect(err).ToNot(HaveOccurred())
req1.Header.Set("Content-Type", "application/json")
req1.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp1, err := client.Do(req1)
Expect(err).ToNot(HaveOccurred())
defer resp1.Body.Close()
// Check if first response succeeded
if resp1.StatusCode != 200 {
Skip("First response failed, skipping item_reference test (backend may not be available)")
}
var response1 map[string]interface{}
body1, err := io.ReadAll(resp1.Body)
Expect(err).ToNot(HaveOccurred())
err = json.Unmarshal(body1, &response1)
Expect(err).ToNot(HaveOccurred())
// Get the first output item ID
output, ok := response1["output"].([]interface{})
Expect(ok).To(BeTrue())
Expect(len(output)).To(BeNumerically(">", 0))
firstItem, ok := output[0].(map[string]interface{})
Expect(ok).To(BeTrue())
itemID, ok := firstItem["id"].(string)
Expect(ok).To(BeTrue())
Expect(itemID).ToNot(BeEmpty())
// Now create a new response with item_reference
reqBody2 := map[string]interface{}{
"model": testModel,
"input": []interface{}{
map[string]interface{}{
"type": "item_reference",
"item_id": itemID,
},
map[string]interface{}{
"type": "message",
"role": "user",
"content": "Continue from the previous message",
},
},
}
payload2, err := json.Marshal(reqBody2)
Expect(err).ToNot(HaveOccurred())
req2, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload2))
Expect(err).ToNot(HaveOccurred())
req2.Header.Set("Content-Type", "application/json")
req2.Header.Set("Authorization", bearerKey)
resp2, err := client.Do(req2)
Expect(err).ToNot(HaveOccurred())
defer resp2.Body.Close()
// Should succeed (item reference resolved)
Expect(resp2.StatusCode).To(Equal(200))
})
It("should return error for invalid item_reference", func() {
reqBody := map[string]interface{}{
"model": testModel,
"input": []interface{}{
map[string]interface{}{
"type": "item_reference",
"item_id": "nonexistent_item_id",
},
},
}
payload, err := json.Marshal(reqBody)
Expect(err).ToNot(HaveOccurred())
req, err := http.NewRequest("POST", "http://127.0.0.1:9090/v1/responses", bytes.NewBuffer(payload))
Expect(err).ToNot(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", bearerKey)
client := &http.Client{}
resp, err := client.Do(req)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
// Should return error
Expect(resp.StatusCode).To(BeNumerically(">=", 400))
})
})
})
})