mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
fix(ai): a ChatGPT turn that searched the web goes on after a tool call
The output items of a step were replayed as they came, web search calls included. With store false the provider cannot resolve such a call again and refused the next step with HTTP 400, which ended the turn with an error right after a hike was proposed. The search calls are left out of the replay; what they found is in the answer's text and its sources.
This commit is contained in:
@@ -38,7 +38,8 @@ type ChatgptTokenSource = (rejected?: string) => Promise<string>;
|
||||
* The provider-neutral history as Responses API input items. An assistant
|
||||
* message this adapter wrote carries its output items, which are replayed as
|
||||
* they came (reasoning with its encrypted content, the function calls with
|
||||
* their namespace), since `store: false` keeps nothing on the provider's side.
|
||||
* their namespace), since `store: false` keeps nothing on the provider's side;
|
||||
* its web search calls are not, as the provider cannot resolve them again.
|
||||
* One written by another adapter is rebuilt from its text and its calls.
|
||||
*/
|
||||
export function toResponsesInput(request: AiChatRequest): Record<string, unknown>[] {
|
||||
@@ -70,7 +71,11 @@ function toItems(message: AiChatMessage): Record<string, unknown>[] {
|
||||
if (message.role === 'tool') {
|
||||
return [{ type: 'function_call_output', call_id: message.toolCallId, output: message.output }];
|
||||
}
|
||||
if (message.providerState?.adapter === 'openai-responses') return message.providerState.items;
|
||||
// A web search the provider ran is left out: with nothing stored it cannot find the call again and
|
||||
// refuses the whole request, and what the search found is in the answer's text and its sources.
|
||||
if (message.providerState?.adapter === 'openai-responses') {
|
||||
return message.providerState.items.filter((item) => item.type !== 'web_search_call');
|
||||
}
|
||||
return [
|
||||
...(message.text
|
||||
? [{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: message.text }] }]
|
||||
|
||||
@@ -474,6 +474,29 @@ describe('OpenAiResponsesClient', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('OARS-003b: leaves its own web search calls out of a replay, which the provider cannot resolve without storage', () => {
|
||||
const search = {
|
||||
type: 'web_search_call',
|
||||
id: 'ws_1',
|
||||
status: 'completed',
|
||||
action: { type: 'search', query: 'Harz' },
|
||||
};
|
||||
const input = toResponsesInput({
|
||||
...REQUEST,
|
||||
context: undefined,
|
||||
messages: [
|
||||
{
|
||||
role: 'assistant',
|
||||
text: 'ignored',
|
||||
toolCalls: [],
|
||||
providerState: { adapter: 'openai-responses', items: [REASONING, search, CALL] },
|
||||
},
|
||||
{ role: 'tool', toolCallId: 'call_x', name: 'create_place', output: 'ok' },
|
||||
],
|
||||
});
|
||||
expect(input).toEqual([REASONING, CALL, { type: 'function_call_output', call_id: 'call_x', output: 'ok' }]);
|
||||
});
|
||||
|
||||
it('OARS-003: replays its own items verbatim and rebuilds another adapter turn from text and calls', () => {
|
||||
const input = toResponsesInput({
|
||||
...REQUEST,
|
||||
|
||||
Reference in New Issue
Block a user