Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 44 additions & 45 deletions gateway/src/api/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class ResponsesAPI extends BaseAPI<ResponseCreateParams, Response, Respon
}

outputMessages = (responseBody: Response): OutputMessages | undefined => {
return responseBody.output.map(mapOutputMessage)
return [mapOutputMessage(responseBody.output)]
}

// SafeExtractor implementation
Expand Down Expand Up @@ -87,7 +87,7 @@ export class ResponsesAPI extends BaseAPI<ResponseCreateParams, Response, Respon
: undefined
},
outputMessages: (responseBody: Response) => {
this.extractedResponse.outputMessages = responseBody.output.map(mapOutputMessage)
this.extractedResponse.outputMessages = [mapOutputMessage(responseBody.output)]
},
}

Expand Down Expand Up @@ -234,55 +234,54 @@ function mapInputMessage(input: ResponseInputItem): ChatMessage {
)
}

function mapOutputMessage(output: ResponseOutputItem): OutputMessage {
return match(output)
.returnType<OutputMessage>()
.with({ content: P.array(P.union({ type: 'output_text' }, { type: 'refusal' })) }, (_output) => {
return {
role: _output.role,
parts: _output.content.map((part) => {
return (
match(part)
function mapOutputMessage(output: ResponseOutputItem[]): OutputMessage {
return { role: 'assistant', parts: mapOutputParts(output) }
}

function mapOutputParts(output: ResponseOutputItem[]): MessagePart[] {
const parts: MessagePart[] = []

for (const item of output) {
match(item)
.with({ type: 'reasoning' }, (item) => {
parts.push({ type: 'thinking' as const, content: item.summary.join('\n') })
})
.with({ content: P.array(P.union({ type: 'output_text' }, { type: 'refusal' })) }, (_item) => {
parts.push(
..._item.content.map((part) => {
return match(part)
.returnType<MessagePart>()
.with({ type: 'output_text' }, (_part) => {
return { type: 'text', content: _part.text }
})
// TODO(Marcelo): How do we represent refusals?
.with({ type: 'refusal' }, (_part) => {
return { type: 'unknown', part: { ..._part } }
})
.exhaustive()
)
}),
}
})
.with({ type: 'function_call' }, (_output) => {
return {
role: 'assistant',
parts: [{ type: 'tool_call', id: _output.call_id, name: _output.name, arguments: _output.arguments }],
}
})
.with({ type: 'reasoning' }, (_output) => {
return {
role: 'assistant',
parts: _output.summary.map((summary) => ({ type: 'thinking', content: summary.text })),
}
})
.with(
P.union(
{ type: 'file_search_call' },
{ type: 'code_interpreter_call' },
{ type: 'image_generation_call' },
{ type: 'local_shell_call' },
{ type: 'computer_call' },
{ type: 'mcp_call' },
{ type: 'mcp_list_tools' },
{ type: 'mcp_approval_request' },
{ type: 'web_search_call' },
),
(_output) => {
return { role: 'assistant', parts: [{ type: 'unknown', part: { ..._output } }] }
},
)
.exhaustive()
}),
)
})
.with({ type: 'function_call' }, (_item) => {
parts.push({ type: 'tool_call', id: _item.call_id, name: _item.name, arguments: _item.arguments })
})
.with(
P.union(
{ type: 'file_search_call' },
{ type: 'code_interpreter_call' },
{ type: 'image_generation_call' },
{ type: 'local_shell_call' },
{ type: 'computer_call' },
{ type: 'mcp_call' },
{ type: 'mcp_list_tools' },
{ type: 'mcp_approval_request' },
{ type: 'web_search_call' },
),
(_item) => {
parts.push({ type: 'unknown', part: { ..._item } })
},
)
.exhaustive()
}

return parts
}
94 changes: 32 additions & 62 deletions gateway/test/providers/openai.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -950,12 +950,12 @@ exports[`openai > openai responses > span 1`] = `
],
"gen_ai.operation.name": "chat",
"gen_ai.output.messages": [
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"content": "Usually blue in daytime, but it can be red/orange at sunrise/sunset, gray when cloudy, and black at night.",
"type": "text",
Expand Down Expand Up @@ -1907,12 +1907,12 @@ exports[`openai > openai responses with builtin tools > span 1`] = `
],
"gen_ai.operation.name": "chat",
"gen_ai.output.messages": [
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"part": {
"code": "import math, decimal
Expand All @@ -1926,15 +1926,10 @@ math.sqrt(n)",
},
"type": "unknown",
},
],
"role": "assistant",
},
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"part": {
"code": "from decimal import Decimal, getcontext
Expand All @@ -1950,15 +1945,10 @@ root",
},
"type": "unknown",
},
],
"role": "assistant",
},
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"part": {
"code": "import math
Expand All @@ -1973,15 +1963,10 @@ s, s*s <= n, (s+1)*(s+1) > n",
},
"type": "unknown",
},
],
"role": "assistant",
},
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"part": {
"code": "s=351_997
Expand All @@ -1994,15 +1979,10 @@ s*s, (s+1)*(s+1)",
},
"type": "unknown",
},
],
"role": "assistant",
},
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"part": {
"code": "from decimal import Decimal, getcontext, ROUND_HALF_UP
Expand All @@ -2021,15 +2001,10 @@ str(rounded)",
},
"type": "unknown",
},
],
"role": "assistant",
},
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"part": {
"code": "from decimal import Decimal, getcontext, ROUND_HALF_UP
Expand All @@ -2046,15 +2021,10 @@ root.quantize(q, rounding=ROUND_HALF_UP)",
},
"type": "unknown",
},
],
"role": "assistant",
},
{
"parts": [],
"role": "assistant",
},
{
"parts": [
{
"content": "",
"type": "thinking",
},
{
"content": "The square root of 123902139123 is approximately 351,997.356698882044317918.",
"type": "text",
Expand Down