Skip to content

Commit 9a0cb64

Browse files
authored
🚀 feat: DashScope cache control enhancement (QwenLM#735)
1 parent 9fce177 commit 9a0cb64

File tree

8 files changed

+446
-87
lines changed

8 files changed

+446
-87
lines changed

packages/core/src/core/openaiContentGenerator/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { Config } from '../../config/config.js';
1212
import { OpenAIContentGenerator } from './openaiContentGenerator.js';
1313
import {
1414
DashScopeOpenAICompatibleProvider,
15+
DeepSeekOpenAICompatibleProvider,
1516
OpenRouterOpenAICompatibleProvider,
1617
type OpenAICompatibleProvider,
1718
DefaultOpenAICompatibleProvider,
@@ -23,6 +24,7 @@ export { ContentGenerationPipeline, type PipelineConfig } from './pipeline.js';
2324
export {
2425
type OpenAICompatibleProvider,
2526
DashScopeOpenAICompatibleProvider,
27+
DeepSeekOpenAICompatibleProvider,
2628
OpenRouterOpenAICompatibleProvider,
2729
} from './provider/index.js';
2830

@@ -61,6 +63,13 @@ export function determineProvider(
6163
);
6264
}
6365

66+
if (DeepSeekOpenAICompatibleProvider.isDeepSeekProvider(config)) {
67+
return new DeepSeekOpenAICompatibleProvider(
68+
contentGeneratorConfig,
69+
cliConfig,
70+
);
71+
}
72+
6473
// Check for OpenRouter provider
6574
if (OpenRouterOpenAICompatibleProvider.isOpenRouterProvider(config)) {
6675
return new OpenRouterOpenAICompatibleProvider(

packages/core/src/core/openaiContentGenerator/pipeline.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -248,26 +248,23 @@ export class ContentGenerationPipeline {
248248
...this.buildSamplingParameters(request),
249249
};
250250

251-
// Let provider enhance the request (e.g., add metadata, cache control)
252-
const enhancedRequest = this.config.provider.buildRequest(
253-
baseRequest,
254-
userPromptId,
255-
);
251+
// Add streaming options if present
252+
if (streaming) {
253+
(
254+
baseRequest as unknown as OpenAI.Chat.ChatCompletionCreateParamsStreaming
255+
).stream = true;
256+
baseRequest.stream_options = { include_usage: true };
257+
}
256258

257259
// Add tools if present
258260
if (request.config?.tools) {
259-
enhancedRequest.tools = await this.converter.convertGeminiToolsToOpenAI(
261+
baseRequest.tools = await this.converter.convertGeminiToolsToOpenAI(
260262
request.config.tools,
261263
);
262264
}
263265

264-
// Add streaming options if needed
265-
if (streaming) {
266-
enhancedRequest.stream = true;
267-
enhancedRequest.stream_options = { include_usage: true };
268-
}
269-
270-
return enhancedRequest;
266+
// Let provider enhance the request (e.g., add metadata, cache control)
267+
return this.config.provider.buildRequest(baseRequest, userPromptId);
271268
}
272269

273270
private buildSamplingParameters(

packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts

Lines changed: 143 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { DashScopeOpenAICompatibleProvider } from './dashscope.js';
1717
import type { Config } from '../../../config/config.js';
1818
import type { ContentGeneratorConfig } from '../../contentGenerator.js';
1919
import { AuthType } from '../../contentGenerator.js';
20+
import type { ChatCompletionToolWithCache } from './types.js';
2021
import { DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES } from '../constants.js';
2122

2223
// Mock OpenAI
@@ -253,17 +254,110 @@ describe('DashScopeOpenAICompatibleProvider', () => {
253254
},
254255
]);
255256

256-
// Last message should NOT have cache control for non-streaming
257+
// Last message should NOT have cache control for non-streaming requests
257258
const lastMessage = result.messages[1];
258259
expect(lastMessage.role).toBe('user');
259260
expect(lastMessage.content).toBe('Hello!');
260261
});
261262

262-
it('should add cache control to both system and last messages for streaming requests', () => {
263+
it('should add cache control to system message only for non-streaming requests with tools', () => {
264+
const requestWithTool: OpenAI.Chat.ChatCompletionCreateParams = {
265+
...baseRequest,
266+
messages: [
267+
{ role: 'system', content: 'You are a helpful assistant.' },
268+
{
269+
role: 'tool',
270+
content: 'First tool output',
271+
tool_call_id: 'call_1',
272+
},
273+
{
274+
role: 'tool',
275+
content: 'Second tool output',
276+
tool_call_id: 'call_2',
277+
},
278+
{ role: 'user', content: 'Hello!' },
279+
],
280+
tools: [
281+
{
282+
type: 'function',
283+
function: {
284+
name: 'mockTool',
285+
parameters: { type: 'object', properties: {} },
286+
},
287+
},
288+
],
289+
stream: false,
290+
};
291+
292+
const result = provider.buildRequest(requestWithTool, 'test-prompt-id');
293+
294+
expect(result.messages).toHaveLength(4);
295+
296+
const systemMessage = result.messages[0];
297+
expect(systemMessage.content).toEqual([
298+
{
299+
type: 'text',
300+
text: 'You are a helpful assistant.',
301+
cache_control: { type: 'ephemeral' },
302+
},
303+
]);
304+
305+
// Tool messages should remain unchanged
306+
const firstToolMessage = result.messages[1];
307+
expect(firstToolMessage.role).toBe('tool');
308+
expect(firstToolMessage.content).toBe('First tool output');
309+
310+
const secondToolMessage = result.messages[2];
311+
expect(secondToolMessage.role).toBe('tool');
312+
expect(secondToolMessage.content).toBe('Second tool output');
313+
314+
// Last message should NOT have cache control for non-streaming requests
315+
const lastMessage = result.messages[3];
316+
expect(lastMessage.role).toBe('user');
317+
expect(lastMessage.content).toBe('Hello!');
318+
319+
// Tools should NOT have cache control for non-streaming requests
320+
const tools = result.tools as ChatCompletionToolWithCache[];
321+
expect(tools).toBeDefined();
322+
expect(tools).toHaveLength(1);
323+
expect(tools[0].cache_control).toBeUndefined();
324+
});
325+
326+
it('should add cache control to system, last history message, and last tool definition for streaming requests', () => {
263327
const request = { ...baseRequest, stream: true };
264-
const result = provider.buildRequest(request, 'test-prompt-id');
328+
const requestWithToolMessage: OpenAI.Chat.ChatCompletionCreateParams = {
329+
...request,
330+
messages: [
331+
{ role: 'system', content: 'You are a helpful assistant.' },
332+
{
333+
role: 'tool',
334+
content: 'First tool output',
335+
tool_call_id: 'call_1',
336+
},
337+
{
338+
role: 'tool',
339+
content: 'Second tool output',
340+
tool_call_id: 'call_2',
341+
},
342+
{ role: 'user', content: 'Hello!' },
343+
],
344+
tools: [
345+
{
346+
type: 'function',
347+
function: {
348+
name: 'mockTool',
349+
parameters: { type: 'object', properties: {} },
350+
},
351+
},
352+
],
353+
};
265354

266-
expect(result.messages).toHaveLength(2);
355+
const result = provider.buildRequest(
356+
requestWithToolMessage,
357+
'test-prompt-id',
358+
);
359+
360+
expect(result.messages).toHaveLength(4);
267361

268362
// System message should have cache control
269363
const systemMessage = result.messages[0];
@@ -275,15 +369,58 @@ describe('DashScopeOpenAICompatibleProvider', () => {
275369
},
276370
]);
277371

278-
// Last message should also have cache control for streaming
279-
const lastMessage = result.messages[1];
372+
// Tool messages should remain unchanged
373+
const firstToolMessage = result.messages[1];
374+
expect(firstToolMessage.role).toBe('tool');
375+
expect(firstToolMessage.content).toBe('First tool output');
376+
377+
const secondToolMessage = result.messages[2];
378+
expect(secondToolMessage.role).toBe('tool');
379+
expect(secondToolMessage.content).toBe('Second tool output');
380+
381+
// Last message should also have cache control
382+
const lastMessage = result.messages[3];
280383
expect(lastMessage.content).toEqual([
281384
{
282385
type: 'text',
283386
text: 'Hello!',
284387
cache_control: { type: 'ephemeral' },
285388
},
286389
]);
390+
391+
const tools = result.tools as ChatCompletionToolWithCache[];
392+
expect(tools).toBeDefined();
393+
expect(tools).toHaveLength(1);
394+
expect(tools[0].cache_control).toEqual({ type: 'ephemeral' });
395+
});
396+
397+
it('should not add cache control to tool messages when request.tools is undefined', () => {
398+
const requestWithoutConfiguredTools: OpenAI.Chat.ChatCompletionCreateParams =
399+
{
400+
...baseRequest,
401+
messages: [
402+
{ role: 'system', content: 'You are a helpful assistant.' },
403+
{
404+
role: 'tool',
405+
content: 'Tool output',
406+
tool_call_id: 'call_1',
407+
},
408+
{ role: 'user', content: 'Hello!' },
409+
],
410+
};
411+
412+
const result = provider.buildRequest(
413+
requestWithoutConfiguredTools,
414+
'test-prompt-id',
415+
);
416+
417+
expect(result.messages).toHaveLength(3);
418+
419+
const toolMessage = result.messages[1];
420+
expect(toolMessage.role).toBe('tool');
421+
expect(toolMessage.content).toBe('Tool output');
422+
423+
expect(result.tools).toBeUndefined();
287424
});
288425

289426
it('should include metadata in the request', () => {

0 commit comments

Comments
 (0)