feat: add default systemPromptMappings for Qwen models

- Add default systemPromptMappings configuration for qwen3-coder-plus model
- Support DashScope compatible mode API endpoints
- Include Qwen coder system prompt template with git repository and sandbox placeholders
- Add comprehensive test coverage for default and custom systemPromptMappings
- Update documentation to reflect the new default configuration behavior
- Ensure backward compatibility with existing user configurations
This commit is contained in:
pomelo-nwu
2025-07-25 12:23:37 +08:00
parent 09bafda05b
commit 782e9d2314
7 changed files with 225 additions and 17 deletions

View File

@@ -154,6 +154,11 @@ export interface ConfigParameters {
temperature?: number;
max_tokens?: number;
};
systemPromptMappings?: Array<{
baseUrls?: string[];
modelNames?: string[];
template?: string;
}>;
}
export class Config {
@@ -204,6 +209,11 @@ export class Config {
temperature?: number;
max_tokens?: number;
};
private readonly systemPromptMappings?: Array<{
baseUrls?: string[];
modelNames?: string[];
template?: string;
}>;
private modelSwitchedDuringSession: boolean = false;
private readonly maxSessionTurns: number;
private readonly listExtensions: boolean;
@@ -258,6 +268,7 @@ export class Config {
this.ideMode = params.ideMode ?? false;
this.enableOpenAILogging = params.enableOpenAILogging ?? false;
this.sampling_params = params.sampling_params;
this.systemPromptMappings = params.systemPromptMappings;
if (params.contextFileName) {
setGeminiMdFilename(params.contextFileName);
@@ -540,6 +551,16 @@ export class Config {
return this.enableOpenAILogging;
}
getSystemPromptMappings():
| Array<{
baseUrls?: string[];
modelNames?: string[];
template?: string;
}>
| undefined {
return this.systemPromptMappings;
}
async refreshMemory(): Promise<{ memoryContent: string; fileCount: number }> {
const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
this.getWorkingDir(),

View File

@@ -238,7 +238,10 @@ export class GeminiClient {
];
try {
const userMemory = this.config.getUserMemory();
const systemInstruction = getCoreSystemPrompt(userMemory);
const systemPromptMappings = this.config.getSystemPromptMappings();
const systemInstruction = getCoreSystemPrompt(userMemory, {
systemPromptMappings,
});
const generateContentConfigWithThinking = isThinkingSupported(
this.config.getModel(),
)
@@ -354,7 +357,10 @@ export class GeminiClient {
model || this.config.getModel() || DEFAULT_GEMINI_FLASH_MODEL;
try {
const userMemory = this.config.getUserMemory();
const systemInstruction = getCoreSystemPrompt(userMemory);
const systemPromptMappings = this.config.getSystemPromptMappings();
const systemInstruction = getCoreSystemPrompt(userMemory, {
systemPromptMappings,
});
const requestConfig = {
abortSignal,
...this.generateContentConfig,
@@ -470,7 +476,10 @@ export class GeminiClient {
try {
const userMemory = this.config.getUserMemory();
const systemInstruction = getCoreSystemPrompt(userMemory);
const systemPromptMappings = this.config.getSystemPromptMappings();
const systemInstruction = getCoreSystemPrompt(userMemory, {
systemPromptMappings,
});
const requestConfig = {
abortSignal,

View File

@@ -18,7 +18,20 @@ import process from 'node:process';
import { isGitRepository } from '../utils/gitUtils.js';
import { MemoryTool, GEMINI_CONFIG_DIR } from '../tools/memoryTool.js';
export function getCoreSystemPrompt(userMemory?: string): string {
export interface ModelTemplateMapping {
baseUrls?: string[];
modelNames?: string[];
template?: string;
}
export interface SystemPromptConfig {
systemPromptMappings?: ModelTemplateMapping[];
}
export function getCoreSystemPrompt(
userMemory?: string,
config?: SystemPromptConfig,
): string {
// if GEMINI_SYSTEM_MD is set (and not 0|false), override system prompt from file
// default path is .qwen/system.md but can be modified via custom path in GEMINI_SYSTEM_MD
let systemMdEnabled = false;
@@ -34,13 +47,49 @@ export function getCoreSystemPrompt(userMemory?: string): string {
throw new Error(`missing system prompt file '${systemMdPath}'`);
}
}
if (
process.env.OPENAI_MODEL?.startsWith('qwen3') &&
process.env.OPENAI_BASE_URL?.includes('dashscope')
) {
const sandbox =
process.env.SANDBOX === 'sandbox-exec' ? 'sandbox-exec' : '';
return `SYSTEM_TEMPLATE:{"name":"qwen3_coder","params":{"is_git_repository":${isGitRepository(process.cwd())},"sandbox":"${sandbox}"}}`;
// Check for system prompt mappings from global config
if (config?.systemPromptMappings) {
const currentModel = process.env.OPENAI_MODEL || '';
const currentBaseUrl = process.env.OPENAI_BASE_URL || '';
const matchedMapping = config.systemPromptMappings.find((mapping) => {
const { baseUrls, modelNames } = mapping;
// Check if baseUrl matches (when specified)
if (
baseUrls &&
modelNames &&
baseUrls.includes(currentBaseUrl) &&
modelNames.includes(currentModel)
) {
return true;
}
if (baseUrls && baseUrls.includes(currentBaseUrl) && !modelNames) {
return true;
}
if (modelNames && modelNames.includes(currentModel) && !baseUrls) {
return true;
}
return false;
});
if (matchedMapping?.template) {
const sandbox =
process.env.SANDBOX === 'sandbox-exec' ? 'sandbox-exec' : '';
const isGitRepo = isGitRepository(process.cwd());
// Replace placeholders in template
let template = matchedMapping.template;
template = template.replace(
'{RUNTIME_VARS_IS_GIT_REPO}',
String(isGitRepo),
);
template = template.replace('{RUNTIME_VARS_SANDBOX}', sandbox);
return template;
}
}
const basePrompt = systemMdEnabled