系统提示词的分层设计:如何通过静态与动态拼接最大化利用大模型缓存

83 阅读8分钟

系统提示词的分层设计:如何通过静态与动态拼接最大化利用大模型缓存

一、引言

系统提示词是Agent开发中最关键的环节。它定义了Agent系统的整体行为框架,涵盖大模型的语气与态度、需执行的职责范围、可调用的工具接口等。本质上,系统提示词是在无状态的大模型接口之上,注入的一套行为指令和上下文约束——我们通过提示词来引导模型的输出方向,使其按照预期方式回应。

然而,随着Agent能力不断增强,系统提示词往往变得冗长复杂(动辄数千甚至上万tokens)。如果每次请求都完整发送,不仅延迟升高,API调用费用也会迅速攀升。如何在不牺牲提示词丰富度的前提下,降低延迟和成本?答案在于大模型服务端的缓存机制。

二、大模型的缓存机制(KV Cache)

2.1 什么是KV Cache?

大模型在生成文本时,需要“记住”此前所有词对应的Key和Value向量,以便在生成下一个词时计算注意力权重。这些KV向量被暂存于GPU内存中,这就是KV Cache。

2.2 缓存复用原理

如果两次请求的前N个词完全相同,则这前N个词计算出的KV向量也完全相同。服务商会将这段“前缀文本”的KV向量暂存于GPU内存或高速磁盘中。当下次请求的前缀匹配(命中缓存)时,模型直接读取缓存的KV结果,仅需计算新增部分的内容,从而:

  • 大幅提升响应速度(跳过重复计算)
  • 降低API调用费用(服务商通常对缓存命中的部分减免计费)

2.3 对提示词设计的启示

缓存命中的前提是前缀完全一致。这意味着:

  • 固定不变的内容(如角色定义、行为准则)应放在提示词最前面
  • 动态变化的内容(如工具列表、用户信息)应放在后面

如果顺序颠倒,动态内容前置会导致每次请求的前缀都不同,缓存将形同虚设。

三、分层设计:静态 + 动态

基于上述缓存原理,系统提示词被自然地划分为两部分:

  • 静态提示词:包含Agent的身份定位、核心职责、行为准则和输出格式等固定内容,始终保持不变。置于最前,作为缓存命中的“锚点”。

  • 动态提示词:根据上下文实时生成,包括当前可用的工具列表、用户信息、输出风格、对话记忆(Memory)、会话轮次提示等。置于静态部分之后,每次请求可根据实际情况灵活变化。

这种分层设计的核心目的,在于最大化复用服务端的缓存结果,在不牺牲灵活性的前提下显著降低延迟和成本。

四、代码实现:以股市智能分析Agent为例

下面以股市智能分析Agent为例,用完整的伪代码示意提示词的分层加载与拼接流程。

4.1 静态提示词定义

const STATIC_PROMPT = `
# 角色定义
你是一位专业的股市智能分析助手,拥有20年证券市场经验,擅长技术分析、基本面分析和市场情绪研判。

# 核心职责
1. 提供A股、港股、美股市场的行情解读与趋势分析
2. 基于技术指标(MACD、KDJ、RSI、均线系统等)给出买卖信号研判
3. 结合财务数据(PE、PB、ROE、营收增长率等)评估公司价值
4. 识别市场风险(政策风险、流动性风险、行业周期风险)并给出警示
5. 回答用户关于股票、ETF、板块、大盘指数的各类问题

# 行为准则
1. 始终保持客观中立,不承诺收益,不诱导交易,明确提示"投资有风险,决策需谨慎"
2. 所有分析必须基于提供的数据,不凭空编造,不确定时明确告知"信息不足"
3. 技术分析需注明时间周期(日线/周线/月线),基本面分析需注明数据来源和时间
4. 多空观点需给出至少2个支撑理由,并同时提示潜在的反向风险
5. 遇到风险事件(财报季、政策发布、黑天鹅)时主动提高风险提示等级

# 输出格式
【结论】- 一句话总结核心观点
【分析依据】- 分点列出支撑理由(技术面/基本面/消息面)
【风险提示】- 列出主要风险因素
【操作建议】- 仅供参考的建议(买入/持有/观望/卖出,附仓位建议)
【免责声明】- 标准投资风险提示

# 语言风格
专业、严谨、数据驱动,使用金融行业标准术语,避免情绪化表达。对于新手用户,在专业术语后附简要解释。
`;

4.2 动态提示词生成

4.2.1 工具列表格式化
/**
 * 格式化工具列表,供模型识别可调用的外部能力
 */
const formatTools = (tools) => {
    const toolDescriptions = tools.map(
        (tool) =>
            `- \`${tool.name}\`: ${tool.description} | 参数: ${JSON.stringify(tool.parameters)}`
    );
    return `# 可用工具\n${toolDescriptions.join('\n')}`;
};
4.2.2 用户信息与风格适配
/**
 * 格式化用户上下文信息
 */
const formatUserInfo = (context) => {
    return `
# 用户信息
- 用户等级:${context.level}
- 风险偏好:${context.riskTolerance}
- 持仓情况:${context.positions || '未提供'}
- 关注板块:${context.focusSectors || '未提供'}
`;
};

/**
 * 根据用户等级返回对应的输出风格指令
 */
const getStyleByLevel = (level) => {
    const styleMap = {
        '初级用户': '请用通俗易懂的语言,对每个专业术语附上简要解释,多使用比喻帮助理解。',
        '普通用户': '保持专业与通俗的平衡,术语适当解释,侧重结论的可操作性。',
        '专业用户': '使用专业术语无需解释,侧重数据细节、技术指标参数和策略逻辑推演。',
        '机构用户': '提供深度分析,关注宏观逻辑、估值模型和风险敞口,输出格式可包含表格。',
    };
    return `# 输出风格\n${styleMap[level] || styleMap['普通用户']}`;
};
4.2.3 对话记忆压缩

为控制上下文长度并保留关键信息,对历史对话进行压缩摘要。此处为短期记忆压缩,实际生产环境可结合向量数据库实现长期记忆检索。

/**
 * 从消息中提取股票代码或名称(示例实现,实际应接入股票代码库或NER服务)
 */
const extractStocksFromMessages = (messages) => {
    // 示例正则:匹配6位数字(A股代码)或2-4位字母(美股代码),以及常见股票名称
    const stockPattern = /([0-9]{6}|[A-Z]{2,4})|(茅台|宁德|腾讯|阿里)/g;
    const stocks = new Set();

    for (const msg of messages) {
        if (!msg.content) continue;
        const matches = msg.content.match(stockPattern);
        if (matches) {
            matches.forEach((m) => stocks.add(m));
        }
    }

    return Array.from(stocks);
};

/**
 * 从历史消息中提取已给出的关键结论
 */
const extractKeyJudgments = (messages) => {
    const judgments = [];
    for (const msg of messages) {
        if (!msg.content) continue;
        if (msg.role === 'assistant') {
            const match = msg.content.match(/【结论】\s*(.+?)(?:\n|$)/);
            if (match) {
                judgments.push(match[1]);
            }
        }
    }
    return judgments.length > 0 ? judgments.join(';') : '无';
};

/**
 * 压缩对话历史,提取关键信息供模型参考
 */
const summarizeHistory = (history) => {
    const maxTurns = 10;
    const recent = history.slice(-maxTurns);

    const stocks = extractStocksFromMessages(recent);
    const stockSummary = stocks.length > 0 ? stocks.join('、') : '无';

    const judgments = extractKeyJudgments(recent);

    return `
# 对话记忆
此前讨论过的标的:${stockSummary}
已给出的关键判断:${judgments || '无'}
`;
};
4.2.4 市场状态构建
/**
 * 构建当前市场状态信息(时间、交易状态、大盘概况等)
 */
const buildMarketContext = () => {
    const now = new Date();
    const dateStr = now.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
    const dayOfWeek = now.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', weekday: 'long' });
    const hour = now.getHours();
    
    // 判断交易状态(A股:工作日 9:30-11:30, 13:00-15:00)
    let tradingStatus = '休市';
    const isWeekday = now.getDay() >= 1 && now.getDay() <= 5;
    if (isWeekday) {
        if ((hour >= 9 && now.getMinutes() >= 30 && hour < 11) || 
            (hour >= 11 && hour < 11) || 
            (hour >= 13 && hour < 15)) {
            tradingStatus = '交易中';
        } else if (hour < 9 || hour >= 15) {
            tradingStatus = '已收盘';
        } else {
            tradingStatus = '休市中';
        }
    }

    return `
# 当前市场状态
- 当前时间:${dateStr}(${dayOfWeek})
- A股交易状态:${tradingStatus}
- 提示:请基于当前时间判断数据时效性,如非交易时间,历史行情数据仅作参考
`;
};
4.2.5 动态提示词组装
/**
 * 组装所有动态模块
 */
const buildDynamicPrompt = (
    userContext,
    conversationHistory,
    tools
) => {
    const parts = [];

    // 1. 工具列表(放在动态部分最前,因工具列表相对稳定,可提升子前缀缓存命中率)
    if (tools && tools.length > 0) {
        parts.push(formatTools(tools));
    }

    // 2. 用户上下文信息
    parts.push(formatUserInfo(userContext));

    // 3. 根据用户等级调整输出风格
    parts.push(getStyleByLevel(userContext.level));

    // 4. 对话记忆压缩
    if (conversationHistory && conversationHistory.length > 0) {
        parts.push(summarizeHistory(conversationHistory));
    }

    // 5. 多轮对话提示
    const turnCount = conversationHistory ? conversationHistory.length : 0;
    if (turnCount > 10) {
        parts.push(`
# 注意
当前对话已进行多轮,请优先基于此前已讨论的标的和逻辑进行分析,避免重复介绍基础概念。
`);
    }

    // 6. 当前市场状态
    parts.push(buildMarketContext());

    return parts.join('\n\n');
};

4.3 系统提示词总拼接

关键决策:静态提示词固定不变,置于最前;动态提示词置于其后。这确保了每次请求的前缀完全一致,从而最大化缓存命中率。

/**
 * 构建完整的系统提示词:静态部分 + 动态部分
 */
const buildSystemPrompt = (
    userContext,
    conversationHistory,
    availableTools
) => {
    const staticPrompt = STATIC_PROMPT;
    const dynamicPrompt = buildDynamicPrompt(
        userContext,
        conversationHistory,
        availableTools
    );

    // 静态在前 → 前缀固定 → 最大化缓存命中
    return `${staticPrompt}\n\n${dynamicPrompt}`;
};

4.4 运行示例

// 注册可用工具
const DEFAULT_TOOLS = [
    {
        name: 'get_stock_price',
        description: '获取实时股价',
        parameters: { code: 'string' },
    },
    {
        name: 'get_financials',
        description: '获取财务报表',
        parameters: { code: 'string', year: 'int' },
    },
    {
        name: 'get_technical_indicators',
        description: '计算技术指标',
        parameters: { code: 'string', period: 'string' },
    },
    {
        name: 'get_news_sentiment',
        description: '获取新闻舆情',
        parameters: { keyword: 'string' },
    },
    {
        name: 'get_sector_performance',
        description: '获取板块表现排名',
        parameters: { market: 'string' },
    },
];

// 用户上下文
const userContext = {
    level: '普通用户',
    riskTolerance: '中',
    positions: '茅台、宁德时代',
    focusSectors: '新能源、消费',
};

// 对话历史
const history = [
    {
        role: 'user',
        content: '帮我分析一下茅台最近的走势',
    },
    {
        role: 'assistant',
        content: '【结论】茅台近期处于震荡整理阶段,多空分歧加大。\n【分析依据】1. 技术面:日线MACD金叉但量能不足...',
    },
];

// 构建完整提示词
const fullPrompt = buildSystemPrompt(
    userContext,
    history,
    DEFAULT_TOOLS
);

console.log(fullPrompt);

五、总结与扩展

以上示例以股市智能分析Agent为场景,展示了系统提示词分层设计的基本思路:

设计要点说明
静态前置固定内容置于最前,确保前缀一致,最大化缓存命中
动态后置可变内容放在后面,保持灵活性
记忆压缩对历史对话提取摘要,控制上下文长度
风格适配根据用户等级动态调整输出风格

在实际生产环境中,提示词的构成往往更加复杂——例如从项目中加载agent.md作为基础人格设定,通过配置文件管理大模型参数,或引入SubAgent协同机制和Hook回调来实现更细粒度的行为控制。

本文侧重于核心的分层设计与缓存优化策略,这些扩展模块的加载与组织方式,可以在后续的Agent框架设计中进一步探讨。核心原则始终不变:不变的放前面,可变的放后面,让缓存为你省时省钱。


文章为个人加AI协同完成,我提供基础加思想,AI负责优化润色,受限于个人水平,文章中若有理解不当之处,恳请各位读者批评指正,不胜感激。


关于我

7年前端开发经验

借此专栏,我希望系统记录自己在 AI Agent 开发领域的思考与实践,以此为媒,寻找下一份工作机会。

📍 坐标:成都
如有合适的岗位或合作机会,欢迎各位大佬推荐或联系,不胜感激!