Skip to content

Commit cbee9ad

Browse files
committed
feat: add basic responses API interceptor
1 parent 59353de commit cbee9ad

20 files changed

Lines changed: 2543 additions & 10 deletions

bridge_integration_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,25 @@ func TestErrorHandling(t *testing.T) {
10901090
require.Contains(t, gjson.GetBytes(body, "error.message").Str, "Input tokens exceed the configured limit")
10911091
},
10921092
},
1093+
{
1094+
name: "responses_API",
1095+
fixture: fixtResponsesUpstreamHttpError,
1096+
createRequestFunc: createOpenAIResponsesReq,
1097+
configureFunc: func(addr string, client aibridge.Recorder, srvProxyMgr *mcp.ServerProxyManager) (*aibridge.RequestBridge, error) {
1098+
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug)
1099+
providers := []aibridge.Provider{provider.NewOpenAI(openaiCfg(addr, apiKey))}
1100+
return aibridge.NewRequestBridge(t.Context(), providers, client, srvProxyMgr, logger, nil, testTracer)
1101+
},
1102+
responseHandlerFn: func(resp *http.Response) {
1103+
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
1104+
body, err := io.ReadAll(resp.Body)
1105+
require.NoError(t, err)
1106+
1107+
require.Equal(t, "mutually_exclusive_parameters", gjson.GetBytes(body, "error.code").Str)
1108+
require.Equal(t, "invalid_request_error", gjson.GetBytes(body, "error.type").Str)
1109+
require.Contains(t, gjson.GetBytes(body, "error.message").Str, "Mutually exclusive parameters: ''. Ensure you are only providing one of: 'pre..._id' or 'conversation'.")
1110+
},
1111+
},
10931112
}
10941113

10951114
for _, tc := range cases {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
-- request --
2+
{
3+
"input": [
4+
{
5+
"role": "user",
6+
"content": "Is 3 + 5 a prime number? Use the add function to calculate the sum."
7+
}
8+
],
9+
"model": "gpt-4.1",
10+
"stream": false,
11+
"tools": [
12+
{
13+
"type": "function",
14+
"name": "add",
15+
"description": "Add two numbers together.",
16+
"parameters": {
17+
"type": "object",
18+
"properties": {
19+
"a": {
20+
"type": "number"
21+
},
22+
"b": {
23+
"type": "number"
24+
}
25+
},
26+
"required": [
27+
"a",
28+
"b"
29+
]
30+
}
31+
}
32+
]
33+
}
34+
35+
-- non-streaming --
36+
{
37+
"id": "resp_0da6045a8b68fa5200695fa23dcc2c81a19c849f627abf8a31",
38+
"object": "response",
39+
"created_at": 1767875133,
40+
"status": "completed",
41+
"background": false,
42+
"billing": {
43+
"payer": "developer"
44+
},
45+
"completed_at": 1767875134,
46+
"error": null,
47+
"incomplete_details": null,
48+
"instructions": null,
49+
"max_output_tokens": null,
50+
"max_tool_calls": null,
51+
"model": "gpt-4.1-2025-04-14",
52+
"output": [
53+
{
54+
"id": "fc_0da6045a8b68fa5200695fa23e198081a19bf68887d47ae93d",
55+
"type": "function_call",
56+
"status": "completed",
57+
"arguments": "{\"a\":3,\"b\":5}",
58+
"call_id": "call_CJSaa2u51JG996575oVljuNq",
59+
"name": "add"
60+
}
61+
],
62+
"parallel_tool_calls": true,
63+
"previous_response_id": null,
64+
"prompt_cache_key": null,
65+
"prompt_cache_retention": null,
66+
"reasoning": {
67+
"effort": null,
68+
"summary": null
69+
},
70+
"safety_identifier": null,
71+
"service_tier": "default",
72+
"store": true,
73+
"temperature": 1.0,
74+
"text": {
75+
"format": {
76+
"type": "text"
77+
},
78+
"verbosity": "medium"
79+
},
80+
"tool_choice": "auto",
81+
"tools": [
82+
{
83+
"type": "function",
84+
"description": "Add two numbers together.",
85+
"name": "add",
86+
"parameters": {
87+
"type": "object",
88+
"properties": {
89+
"a": {
90+
"type": "number"
91+
},
92+
"b": {
93+
"type": "number"
94+
}
95+
},
96+
"required": [
97+
"a",
98+
"b"
99+
],
100+
"additionalProperties": false
101+
},
102+
"strict": true
103+
}
104+
],
105+
"top_logprobs": 0,
106+
"top_p": 1.0,
107+
"truncation": "disabled",
108+
"usage": {
109+
"input_tokens": 58,
110+
"input_tokens_details": {
111+
"cached_tokens": 0
112+
},
113+
"output_tokens": 18,
114+
"output_tokens_details": {
115+
"reasoning_tokens": 0
116+
},
117+
"total_tokens": 76
118+
},
119+
"user": null,
120+
"metadata": {}
121+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
-- request --
2+
{
3+
"conversation": "conv_695fa15ecbb881958e89ac2d35d918ed0c9f1f0524a858fa",
4+
"input": "explain why this is funny.",
5+
"model": "gpt-4o-mini",
6+
"stream": false
7+
}
8+
9+
10+
-- non-streaming --
11+
{
12+
"id": "resp_0c9f1f0524a858fa00695fa15fc5a081958f4304aafd3bdec2",
13+
"object": "response",
14+
"created_at": 1767874911,
15+
"status": "completed",
16+
"background": false,
17+
"billing": {
18+
"payer": "developer"
19+
},
20+
"completed_at": 1767874914,
21+
"conversation": {
22+
"id": "conv_695fa15ecbb881958e89ac2d35d918ed0c9f1f0524a858fa"
23+
},
24+
"error": null,
25+
"incomplete_details": null,
26+
"instructions": null,
27+
"max_output_tokens": null,
28+
"max_tool_calls": null,
29+
"model": "gpt-4o-mini-2024-07-18",
30+
"output": [
31+
{
32+
"id": "msg_0c9f1f0524a858fa00695fa1605bd48195b65b4dfd732941bc",
33+
"type": "message",
34+
"status": "completed",
35+
"content": [
36+
{
37+
"type": "output_text",
38+
"annotations": [],
39+
"logprobs": [],
40+
"text": "This joke plays on a double meaning of the phrase \u201cmake up.\u201d \n\n1. **Literal Meaning**: Atoms are the basic building blocks of matter and literally \"make up\" all substances in the universe.\n\n2. **Figurative Meaning**: The phrase \"make up\" can also mean to fabricate or lie about something. \n\nThe humor comes from the unexpected twist; it starts off sounding like a serious statement about atoms, then surprises us with a clever play on words that suggests atoms are dishonest. This blend of scientific fact and pun creates the comedic effect!"
41+
}
42+
],
43+
"role": "assistant"
44+
}
45+
],
46+
"parallel_tool_calls": true,
47+
"previous_response_id": null,
48+
"prompt_cache_key": null,
49+
"prompt_cache_retention": null,
50+
"reasoning": {
51+
"effort": null,
52+
"summary": null
53+
},
54+
"safety_identifier": null,
55+
"service_tier": "default",
56+
"store": true,
57+
"temperature": 1.0,
58+
"text": {
59+
"format": {
60+
"type": "text"
61+
},
62+
"verbosity": "medium"
63+
},
64+
"tool_choice": "auto",
65+
"tools": [],
66+
"top_logprobs": 0,
67+
"top_p": 1.0,
68+
"truncation": "disabled",
69+
"usage": {
70+
"input_tokens": 48,
71+
"input_tokens_details": {
72+
"cached_tokens": 0
73+
},
74+
"output_tokens": 116,
75+
"output_tokens_details": {
76+
"reasoning_tokens": 0
77+
},
78+
"total_tokens": 164
79+
},
80+
"user": null,
81+
"metadata": {}
82+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
-- request --
2+
{
3+
"input": "explain why this is funny.",
4+
"model": "gpt-4o-mini",
5+
"previous_response_id": "resp_0388c79043df3e3400695f9f83cd6481959062cec6830d8d51",
6+
"stream": false
7+
}
8+
9+
-- non-streaming --
10+
{
11+
"id": "resp_0388c79043df3e3400695f9f86cfa08195af1f015c60117a83",
12+
"object": "response",
13+
"created_at": 1767874438,
14+
"status": "completed",
15+
"background": false,
16+
"billing": {
17+
"payer": "developer"
18+
},
19+
"completed_at": 1767874441,
20+
"error": null,
21+
"incomplete_details": null,
22+
"instructions": null,
23+
"max_output_tokens": null,
24+
"max_tool_calls": null,
25+
"model": "gpt-4o-mini-2024-07-18",
26+
"output": [
27+
{
28+
"id": "msg_0388c79043df3e3400695f9f87369c8195a0d1a82a06f96d56",
29+
"type": "message",
30+
"status": "completed",
31+
"content": [
32+
{
33+
"type": "output_text",
34+
"annotations": [],
35+
"logprobs": [],
36+
"text": "The joke plays on a clever wordplay and a double meaning. \n\n1. **Outstanding in his field**: The phrase can mean that someone is exceptionally good at what they do (outstanding performance) and also literally refers to the scarecrow being in a field (like a farm field). \n\n2. **Scarecrow context**: Scarecrows are placed in fields to scare away birds, so the idea of a scarecrow being \"outstanding\" can lead to a funny mental image.\n\nThe humor comes from the unexpected twist of a literal phrase being interpreted in a figurative way, creating a light and playful pun."
37+
}
38+
],
39+
"role": "assistant"
40+
}
41+
],
42+
"parallel_tool_calls": true,
43+
"previous_response_id": "resp_0388c79043df3e3400695f9f83cd6481959062cec6830d8d51",
44+
"prompt_cache_key": null,
45+
"prompt_cache_retention": null,
46+
"reasoning": {
47+
"effort": null,
48+
"summary": null
49+
},
50+
"safety_identifier": null,
51+
"service_tier": "default",
52+
"store": true,
53+
"temperature": 1.0,
54+
"text": {
55+
"format": {
56+
"type": "text"
57+
},
58+
"verbosity": "medium"
59+
},
60+
"tool_choice": "auto",
61+
"tools": [],
62+
"top_logprobs": 0,
63+
"top_p": 1.0,
64+
"truncation": "disabled",
65+
"usage": {
66+
"input_tokens": 43,
67+
"input_tokens_details": {
68+
"cached_tokens": 0
69+
},
70+
"output_tokens": 129,
71+
"output_tokens_details": {
72+
"reasoning_tokens": 0
73+
},
74+
"total_tokens": 172
75+
},
76+
"user": null,
77+
"metadata": {}
78+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
-- request --
2+
{
3+
"input": "tell me a joke",
4+
"model": "gpt-4o-mini",
5+
"stream": false
6+
}
7+
8+
-- non-streaming --
9+
{
10+
"id": "resp_0388c79043df3e3400695f9f83cd6481959062cec6830d8d51",
11+
"object": "response",
12+
"created_at": 1767874435,
13+
"status": "completed",
14+
"background": false,
15+
"billing": {
16+
"payer": "developer"
17+
},
18+
"completed_at": 1767874436,
19+
"error": null,
20+
"incomplete_details": null,
21+
"instructions": null,
22+
"max_output_tokens": null,
23+
"max_tool_calls": null,
24+
"model": "gpt-4o-mini-2024-07-18",
25+
"output": [
26+
{
27+
"id": "msg_0388c79043df3e3400695f9f8447a08195af2ef951966823c4",
28+
"type": "message",
29+
"status": "completed",
30+
"content": [
31+
{
32+
"type": "output_text",
33+
"annotations": [],
34+
"logprobs": [],
35+
"text": "Why did the scarecrow win an award?\n\nBecause he was outstanding in his field!"
36+
}
37+
],
38+
"role": "assistant"
39+
}
40+
],
41+
"parallel_tool_calls": true,
42+
"previous_response_id": null,
43+
"prompt_cache_key": null,
44+
"prompt_cache_retention": null,
45+
"reasoning": {
46+
"effort": null,
47+
"summary": null
48+
},
49+
"safety_identifier": null,
50+
"service_tier": "default",
51+
"store": true,
52+
"temperature": 1.0,
53+
"text": {
54+
"format": {
55+
"type": "text"
56+
},
57+
"verbosity": "medium"
58+
},
59+
"tool_choice": "auto",
60+
"tools": [],
61+
"top_logprobs": 0,
62+
"top_p": 1.0,
63+
"truncation": "disabled",
64+
"usage": {
65+
"input_tokens": 11,
66+
"input_tokens_details": {
67+
"cached_tokens": 0
68+
},
69+
"output_tokens": 18,
70+
"output_tokens_details": {
71+
"reasoning_tokens": 0
72+
},
73+
"total_tokens": 29
74+
},
75+
"user": null,
76+
"metadata": {}
77+
}

0 commit comments

Comments
 (0)