提示词工程与工具调用入门

AI Agent 工程实践教程 · 第 05 章

把提示词写法、结构化输出和工具调用连接起来,形成可复用的 Agent 输入输出规范。

返回系列目录

第五天:提示词工程入门

前面几天我们已经完成了学生端 AI 问答的核心链路:

  1. 前端输入问题。
  2. Java 保存消息,并把问题转发给 Python。
  3. Python 判断是否需要知识库。
  4. 需要时通过 LlamaIndex 和 Milvus 检索资料。
  5. Python 把系统提示词、知识库资料、历史消息、本次问题组装成 messages
  6. 大模型根据 messages 生成回答。
  7. Java 接收流式结果,保存 AI 回答,再转发给前端。

第五天开始,我们要把注意力放到一个很关键的问题上:

同样是调用大模型,为什么有的回答稳定、清楚、能按要求输出,有的回答却经常不符合预期?

答案很大程度上和提示词有关。

提示词工程不是简单地“把问题写长一点”,而是让模型明确知道:

  1. 它现在是什么角色。
  2. 它要完成什么任务。
  3. 它可以参考哪些上下文。
  4. 它必须遵守哪些规则。
  5. 它应该用什么格式输出。
  6. 程序后面要怎么使用它的输出。

这一天我们先讲提示词,把模型为什么能按规则回答、怎么按格式输出、提示词为什么要做版本管理讲清楚。

1. Prompt 不是一句话,而是一组 messages

很多同学第一次接触大模型时,会把 Prompt 理解成“用户输入的那一句话”。

例如:

什么是 RAG?

这句话当然是 Prompt 的一部分,但在真实项目里,大模型通常收到的不是一个字符串,而是一组 messages

每一条 message 都有两个核心字段:

{
  "role": "system",
  "content": "你是一个耐心、专业的 AI 课程助教。"
}

role 表示这条消息的角色,content 表示具体内容。

一次模型请求的 messages 结构

我们项目里的角色主要有三种:

role 含义 项目中的作用
system 系统提示词 规定模型身份、回答风格、知识库使用规则
user 用户提示词 学生本次真正提出的问题
assistant AI 历史回答 多轮对话时,让模型知道前面已经回答过什么

这里要注意一个重点:

模型会按照 messages 的顺序理解上下文。

所以系统规则、知识库资料、历史消息、本次问题的顺序不能随便放。

2. 系统提示词是什么

在一个 AI 应用里,系统提示词的作用是先给模型定规则。

用户每次问的问题都不一样,但产品希望 AI 的表现是稳定的:

  1. 始终以同一个身份回答。
  2. 始终保持合适的语气。
  3. 始终遵守业务边界。
  4. 始终按照项目要求组织答案。

比如我们的学生端 AI 问答,不希望模型一会儿像百科,一会儿像客服,一会儿又像普通聊天助手。

我们希望它一直像一个“AI 课程助教”:

  1. 能用学生听得懂的方式解释问题。
  2. 回答时尽量结合课程和项目。
  3. 遇到知识库没有说明的内容,要明确告诉学生。
  4. 不把没有依据的信息说成确定结论。

所以,系统提示词不是为了装饰模型,而是为了给模型一个稳定的工作方式。

系统提示词,也就是 system prompt,用来告诉模型:

  1. 你是谁。
  2. 你应该用什么语气回答。
  3. 你回答时要遵守哪些边界。
  4. 什么内容可以回答,什么内容需要谨慎。

我们项目里有一个默认系统提示词,配置在 Python 的 Settings 里:

ai_chat_system_prompt: str = os.getenv(
    "AI_CHAT_SYSTEM_PROMPT",
    "你是一个耐心、专业的 AI 课程助教,请用清晰、适合学生理解的方式回答问题。",
)

这句话虽然不长,但已经告诉模型三件事:

  1. 角色:AI 课程助教。
  2. 态度:耐心、专业。
  3. 表达方式:清晰、适合学生理解。

如果没有系统提示词,模型只看到用户问题,也可以回答,但回答风格会更不稳定。

例如用户问:

RAG 是什么?

如果没有系统提示词,模型可能用很通用的百科式语言回答。

如果加上系统提示词:

你是一个耐心、专业的 AI 课程助教,请用清晰、适合学生理解的方式回答问题。

模型就更容易用课程讲解的方式回答。

3. 用户提示词是什么

用户提示词,也就是 user prompt,是用户本次真正输入的问题。

在学生端 AI 问答里,它来自页面输入框。

例如:

老师,向量数据库为什么不用 MySQL?

到了 Python 服务后,它会被放进最后一条 user message:

messages.append(ChatMessage(role="user", content=question))

用户提示词一般放在 messages 的最后。

原因很简单:

前面的系统规则、历史消息、知识库资料都是为了帮助模型理解上下文,最后这条用户消息才是模型本次要回答的问题。

4. assistant 消息是什么

assistant 消息表示模型之前说过的话。

在多轮对话里,模型需要知道前面聊了什么。

例如第一轮:

user: 什么是 RAG?
assistant: RAG 是检索增强生成...

第二轮用户继续问:

它和普通大模型问答有什么区别?

这里的“它”指的是上一轮的 RAG。

如果不给历史消息,模型可能不知道“它”指什么。

所以项目里 Java 会查询最近的历史消息,然后传给 Python:

history: list[ChatMessage] = Field(default_factory=list)

Python 在组装 messages 时,会把历史消息放进去:

messages.extend(history)

这样模型就能结合上下文回答。

5. 我们项目里的 messages 顺序

学生端 AI 问答真正拼 Prompt 的位置在 Python:

YanQue-AI/src/yanque_ai/chat/prompt_builder.py

核心代码是:

messages = [
    ChatMessage(role="system", content=self._settings.ai_chat_system_prompt),
]
if knowledge_hits:
    messages.append(ChatMessage(role="system", content=self._build_knowledge_prompt(knowledge_hits)))
messages.extend(history)
messages.append(ChatMessage(role="user", content=question))

它的顺序是:

  1. 先放系统提示词。
  2. 如果查到了知识库资料,再放知识库提示词。
  3. 再放历史对话。
  4. 最后放学生本次问题。

也可以画成这样:

system:你是 AI 课程助教
system:下面是知识库检索到的参考资料
user/assistant:历史对话
user:学生本次问题

这个顺序很重要。

如果把知识库资料放在用户问题后面,模型不一定能很好地把它当成参考资料。

如果把历史消息放在系统提示词前面,规则的优先级也会变得不清楚。

6. 知识库提示词怎么写

我们项目里的知识库提示词不是直接把 chunk 拼进去,而是先加一段规则:

return (
    "下面是从知识库检索到的参考资料。回答时优先依据这些资料;"
    "资料没有提到的内容,要明确说明“知识库资料里没有明确说明”,不要编造。\n\n"
    + "\n\n".join(chunks)
)

这段提示词做了两件事:

  1. 告诉模型这些内容是参考资料。
  2. 告诉模型资料没有提到时不要自己补充没有依据的内容。

这就是 RAG 问答里非常重要的一类提示词。

如果只把资料内容塞给模型,不说明使用规则,模型可能会把知识库资料和自己的通用知识混在一起回答。

所以 RAG 场景的提示词通常要写清楚:

  1. 优先依据检索资料。
  2. 资料没有说明时,要明确告诉用户。
  3. 不要把不确定的信息说成确定结论。
  4. 回答要围绕用户问题,不要把所有资料都复述一遍。

一个更完整的 RAG 系统提示词可以这样写:

你是一个 AI 课程助教。
下面会提供若干段从知识库检索到的资料。
回答时请优先依据资料内容。
如果资料中没有明确说明,请直接说明“知识库资料里没有明确说明”。
不要把没有依据的内容说成确定事实。
回答要简洁、清楚,适合学生理解。

7. 什么是一个好的提示词

一个好的提示词,不是越长越好,而是越清楚越好。

一个好提示词的基本结构

可以按照下面这个结构来写:

角色:你是谁?
任务:你要做什么?
上下文:你可以参考哪些资料?
规则:你必须遵守什么?
输出格式:你要怎么输出?
示例:复杂任务给一个例子。

例如我们要让模型讲解一个技术概念,可以这样写:

你是一个 AI 课程助教。
请用适合初学者的方式解释下面的技术概念。
要求:
1. 先用一句话说明它是什么。
2. 再用一个生活中的例子帮助理解。
3. 最后说明它在我们项目里对应哪一部分。
4. 不要使用过多专业术语。

学生问题:什么是向量数据库?

这个提示词比单纯问“什么是向量数据库”更稳定。

因为它告诉模型:

  1. 用什么身份回答。
  2. 按什么结构回答。
  3. 面向什么水平的人回答。
  4. 回答重点要落到项目里。

8. 提示词里要少用模糊要求

下面这些要求比较模糊:

回答得好一点。
回答得专业一点。
写得详细一点。
不要编造没有依据的内容。

它们不是完全不能用,但模型不一定知道“好”“专业”“详细”具体是什么意思。

更好的写法是把要求拆清楚:

请按下面结构回答:
1. 先用一句话解释概念。
2. 再说明它解决了什么问题。
3. 再结合我们项目里的代码位置说明它怎么落地。
4. 最后给一个简单例子。

如果资料中没有明确说明,请直接说明资料没有提到。

提示词越具体,模型越容易按预期输出。

9. 系统提示词和用户提示词的区别

系统提示词和用户提示词都能影响模型,但它们的定位不一样。

类型 谁来维护 放在哪里 主要作用
系统提示词 项目开发者或运营人员 system message 规定模型身份、风格、边界
用户提示词 最终用户 user message 表达本次问题或任务

举个例子:

系统提示词:

你是一个耐心、专业的 AI 课程助教,请用清晰、适合学生理解的方式回答问题。

用户提示词:

LlamaIndex 在我们项目里负责什么?

模型最终回答时,会同时参考这两部分。

系统提示词更像“长期规则”,用户提示词更像“本次任务”。

10. 结构化输出提示词

有些时候,我们不是让模型直接写一段自然语言回答,而是让它输出一个结构化结果。

我们项目里的知识库路由就是一个例子。

代码位置:

YanQue-AI/src/yanque_ai/chat/knowledge_router.py

它要让一个轻量模型判断:

这个问题需不需要查知识库?

系统提示词里写了这样的规则:

你是一个问答路由分类器,只判断用户问题是否需要检索公司内部知识库。
不确定时返回 true。只输出 JSON,不要输出其他文字。

用户提示词里要求模型按固定结构返回:

请判断下面问题是否需要检索知识库,并返回:
{"useKnowledgeBase": true/false, "reason": "一句话原因", "confidence": 0-1}
问题:...

这里模型输出的不是最终回答,而是一个 JSON。

程序再读取这个 JSON:

data = self._parse_json_object(content)
use_knowledge = bool(data.get("useKnowledgeBase"))

这类提示词在后面讲工具调用时非常重要。

因为工具调用的核心也是:

  1. 让模型判断要不要使用工具。
  2. 让模型选择哪个工具。
  3. 让模型给出工具需要的参数。
  4. 程序校验参数,然后真正执行工具。

11. 结构化输出一定要做程序校验

让模型“只输出 JSON”,不代表它永远都会输出合法 JSON。

模型有时可能多输出一句解释,也可能少一个字段。

所以项目里没有直接相信模型输出,而是做了提取和解析:

match = re.search(r"\{.*\}", content, flags=re.S)
if not match:
    raise ValueError("模型未返回 JSON")
return json.loads(match.group(0))

同时外层也做了异常处理:

except Exception:
    return True, "知识库路由判断失败,保守使用知识库"

这是一条很重要的工程原则:

提示词负责引导模型,程序负责校验结果。

不能因为提示词写了“必须返回 JSON”,代码里就完全不处理异常。

12. Few-shot:给模型一个例子

Few-shot 指的是在提示词里给模型一个或几个示例。

如果任务比较复杂,示例会比单纯写规则更直观。

例如我们要判断是否需要知识库,可以这样写:

你是一个问答路由分类器,只判断用户问题是否需要检索知识库。

示例 1:
问题:Python 的 list 和 tuple 有什么区别?
输出:{"useKnowledgeBase": false, "reason": "这是通用编程知识", "confidence": 0.9}

示例 2:
问题:第三天课程讲义里 RAG 是怎么解释的?
输出:{"useKnowledgeBase": true, "reason": "问题依赖课程内部资料", "confidence": 0.95}

现在请判断:
问题:...

Few-shot 的好处是模型更容易模仿输出格式和判断标准。

但也要注意,示例不要太多。

示例太多会占用上下文,也会增加模型理解成本。

13. 为什么提示词要放到数据库

现在我们项目里的系统提示词主要通过环境变量配置:

AI_CHAT_SYSTEM_PROMPT=你是一个耐心、专业的 AI 课程助教...

这种方式适合早期开发,因为简单直接。

但如果项目里有多个 Agent,就不能只考虑一个系统提示词。

不同 Agent 的工作目标不同,系统提示词也应该不同。

例如:

Agent 系统提示词重点
学生问答 Agent 像 AI 课程助教一样回答,语言清楚,适合学生理解
知识库路由 Agent 只判断问题是否需要检索知识库,输出固定 JSON
工具调用 Agent 判断是否需要调用工具,并生成工具参数
作业批改 Agent 按评分标准给出评价、分数和修改建议
客服问答 Agent 按业务口径回答,遇到不确定问题要提示人工处理

也就是说,系统提示词不是全项目只有一份。

它通常会随着 Agent 的职责、业务场景和输出格式发生变化。

但当项目上线后,提示词可能会经常调整:

  1. 想让回答更适合学生。
  2. 想加一条知识库回答规则。
  3. 想调整结构化输出格式。
  4. 想对比不同版本的回答效果。
  5. 某次调整效果不好,需要快速回滚。

如果提示词一直写在代码或环境变量里,每次修改都需要开发介入,管理起来不方便。

所以更完整的做法是把提示词放进数据库。

这也属于提示词工程的一部分。

它不是在研究“怎么写一句 Prompt”,而是在解决:

提示词如何被管理、发布、审计和回滚。

提示词版本管理流程

14. 提示词表可以怎么设计

可以先设计两张表。

第一张表:prompt_template

保存一个提示词的基本信息,以及它属于哪个 Agent。

CREATE TABLE prompt_template (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(100) NOT NULL COMMENT '提示词编码,例如 ai_chat_system',
    name VARCHAR(100) NOT NULL COMMENT '提示词名称',
    agent_code VARCHAR(100) NOT NULL COMMENT '所属 Agent,例如 student_chat_agent',
    status VARCHAR(20) NOT NULL COMMENT '状态:ENABLED/DISABLED',
    active_version_id BIGINT NULL COMMENT '当前启用的版本 ID',
    create_time DATETIME NOT NULL,
    update_time DATETIME NOT NULL,
    UNIQUE KEY uk_prompt_template_code (code)
);

第二张表:prompt_template_version

保存每一次提示词内容修改。

CREATE TABLE prompt_template_version (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    template_id BIGINT NOT NULL COMMENT '所属提示词模板 ID',
    version_no INT NOT NULL COMMENT '版本号',
    content TEXT NOT NULL COMMENT '提示词内容',
    variables JSON NULL COMMENT '变量说明,例如 question、knowledge_context',
    change_note VARCHAR(500) NULL COMMENT '本次修改说明',
    create_by BIGINT NULL COMMENT '创建人',
    create_time DATETIME NOT NULL,
    UNIQUE KEY uk_prompt_template_version (template_id, version_no)
);

这两张表先解决最核心的问题:

  1. prompt_template:这个提示词是什么。
  2. prompt_template_version:它有哪些历史版本。

有了版本以后,提示词就可以回滚。

比如学生问答 Agent 原来使用的是第 2 版提示词:

prompt_template.active_version_id -> version 2

后来我们升级了一版提示词,希望回答更详细:

prompt_template.active_version_id -> version 3

但上线后发现第 3 版效果不好,例如回答变得太长,或者不够适合学生理解。

这时不需要删除第 3 版,也不需要马上改代码。

只要把当前启用版本切回第 2 版:

prompt_template.active_version_id -> version 2

这就是提示词回滚。

所以版本管理的价值不只是“保存历史记录”,更重要的是:

  1. 新版本可以上线试用。
  2. 旧版本不会丢失。
  3. 新版本效果不好时,可以快速切回稳定版本。

15. 提示词要不要加缓存

提示词放进数据库以后,还有一个运行时问题:

每次学生提问时,Python 都去数据库查提示词,会不会变慢?

如果访问量很小,直接查数据库也能跑。

课程第一版可以先不用 Redis,直接在 Python 服务里做本地缓存。

数据库保存提示词配置
Python 本地内存缓存当前启用的提示词
Python 调用模型前优先读本地缓存

也就是说,数据库是提示词的管理源,本地缓存是提示词的读取加速层。

可以这样理解:

  1. 管理后台修改提示词,写入 MySQL。
  2. Python 第一次处理学生问题时,从 MySQL 查询当前启用的提示词。
  3. 查询到以后,把提示词放到 Python 本地缓存里。
  4. 后面的请求优先读本地缓存,不需要每次都查 MySQL。
  5. 缓存过期后,再重新查 MySQL,并刷新本地缓存。

这样做的好处是:

  1. 不需要每次问答都访问 MySQL。
  2. 不需要额外引入 Redis,课程实现更简单。
  3. 提示词修改或回滚后,缓存过期就会读到新的当前版本。

缓存的 key 可以设计得简单一点:

prompt:active:{agent_code}:{prompt_code}

例如:

prompt:active:student_chat_agent:ai_chat_system

缓存里可以保存当前启用版本的内容和过期时间。

如果使用第三方库,可以用 cachetools.TTLCache

from cachetools import TTLCache

prompt_cache = TTLCache(maxsize=100, ttl=60)

这里的含义是:

  1. maxsize=100:最多缓存 100 条提示词。
  2. ttl=60:每条缓存最多保留 60 秒。

读取时可以按 agent_codeprompt_code 组成缓存 key:

cache_key = f"prompt:active:{agent_code}:{prompt_code}"

prompt = prompt_cache.get(cache_key)
if prompt is None:
    prompt = query_prompt_from_mysql(agent_code, prompt_code)
    prompt_cache[cache_key] = prompt

缓存中的提示词内容可以长这样:

{
  "templateId": 1,
  "versionNo": 2,
  "content": "你是一个耐心、专业的 AI 课程助教..."
}

这样 Python 每次组装 messages 时,就可以直接拿到当前生效的系统提示词。

如果不想引入第三方库,也可以自己用字典保存过期时间:

import time

prompt_cache = {}

def get_cached_prompt(cache_key: str):
    item = prompt_cache.get(cache_key)
    if not item:
        return None

    if item["expires_at"] < time.time():
        prompt_cache.pop(cache_key, None)
        return None

    return item["value"]

这种写法的核心就是:

  1. 缓存时多保存一个 expires_at
  2. 读取时先判断有没有过期。
  3. 过期了就删除缓存,再查数据库。

课程里推荐先用 cachetools.TTLCache,代码更短,也更容易讲清楚。

16. 怎么判断提示词升级有没有效果

提示词升级以后,不能只靠“感觉好像更好了”来判断。

提示词升级评估流程

比较清楚的做法可以分成四步。

第一步:先确定这次升级要解决什么问题

提示词升级之前,要先写清楚升级目标。

例如:

本次升级目标:
当知识库没有相关资料时,模型要明确说明“知识库资料里没有明确说明”,不能补充没有依据的内容。

有了明确目标,后面才知道应该测什么。

如果目标没有说清楚,评估时就容易变成“看起来好像更好”,这样很难判断是否真的可以发布。

第二步:准备两类测试数据

测试数据一般分成两类:

  1. 本次升级数据。
  2. 回归数据。

本次升级数据,用来验证这次要修的问题有没有修好。

例如这次升级是为了解决“知识库没有资料时不能补充没有依据内容”,测试数据就要包含这类问题:

数据类型 测试问题 期望表现
本次升级数据 知识库里有第五天工具调用的完整代码吗? 如果资料没有提到,要明确说明
本次升级数据 第六天会讲什么? 如果知识库没有资料,不能直接编造

回归数据,用来验证原来正常的问题有没有被影响。

例如旧版本在下面这些问题上回答得不错,新版本也不能变差:

数据类型 测试问题 期望表现
回归数据 RAG 是什么? 仍然能用学生听得懂的方式解释
回归数据 向量数据库为什么不用 MySQL? 仍然能讲清楚业务数据和向量数据的区别
回归数据 LlamaIndex 在项目里负责什么? 仍然能结合项目代码说明
回归数据 第四天讲了哪些内容? 仍然会优先参考知识库

简单说:

本次升级数据:看新问题有没有修好
回归数据:看旧能力有没有被影响

测试数据不要只放简单问题,最好覆盖几种典型情况:

类型 目的 示例
正常问题 看基础回答是否稳定 RAG 是什么?
知识库存在的问题 看是否能依据资料回答 第四天讲了哪些内容?
知识库不存在的问题 看是否会说明资料没有明确说明 第六天讲义里工具调用怎么实现?
错误前提问题 看是否会纠正错误前提 Milvus 是不是用来保存 MySQL 表的?
边界问题 看是否能控制回答范围 你能保证这个答案一定正确吗?
格式要求问题 看是否能按格式输出 请用三点总结 LlamaIndex 的作用
多轮追问问题 看历史上下文是否生效 那它和 Milvus 的区别呢?

第三步:让旧版本和新版本回答同一批问题

评估时要让旧版本和新版本面对同一批测试数据。

version 2 -> 回答 A
version 3 -> 回答 B

这样对比才公平。

如果旧版本测的是一批问题,新版本测的是另一批问题,就很难判断差异到底来自提示词,还是来自测试问题本身。

第四步:自动评估为主,人工抽查兜底

少量关键问题可以人工评分。

但测试数据一多,人工不可能全部看完,所以更常见的方式是:

自动评估全量数据
人工复查异常样本
人工抽查正常样本
最后决定是否发布

自动评估可以分成两层。

第一层是程序规则检查。

这类检查比较确定,适合交给代码做。

检查项 判断方式
是否输出合法 JSON json.loads 判断能不能解析
是否包含必要字段 检查字段是否存在
回答是否太长 统计字数
是否按要求列出三点 检查结构数量

第二层是大模型评估。

对于“是否适合学生理解”“是否回答到重点”“是否遵守知识库边界”这类语义问题,可以让另一个大模型做初步评分。

这种方式也常叫 LLM as Judge,意思是让大模型当评审。

评分 Prompt 可以这样写:

你是提示词升级评估员。
请比较旧版本回答和新版本回答。

评分维度:
1. 准确性:有没有说错。
2. 完整性:有没有回答到问题重点。
3. 可理解性:是否适合学生理解。
4. 知识库边界:资料没有说明时,是否避免补充没有依据的内容。
5. 回归风险:新版本是否比旧版本变差。

请只输出 JSON:
{
  "oldScore": 1-5,
  "newScore": 1-5,
  "better": "old/new/same",
  "risk": "low/medium/high",
  "reason": "一句话说明原因"
}

大模型评估不能完全代替人工。

它的作用是先帮我们筛选:

  1. 新版本分数明显下降的样本。
  2. 新旧版本差异很大的样本。
  3. 风险等级为 mediumhigh 的样本。
  4. 本次升级数据没有通过的样本。

这些样本再交给人工重点复查。

同时,也要从正常样本里随机抽查一部分,避免自动评估漏掉问题。

最后可以按下面规则决定是否发布:

情况 处理方式
本次升级数据明显变好,回归数据没有明显变差 可以发布
本次升级数据变好,但回归数据变差 暂不发布,继续修改
本次升级数据没有变好 暂不发布
自动评估通过,但人工抽查发现明显问题 暂不发布

课程第一版先记住一句话:

提示词升级不是只看新问题有没有修好,还要看旧能力有没有被影响;大量数据先用大模型和规则自动评估,再由人工复查重点样本。

17. 提示词里的变量

很多提示词不是固定文本,而是模板。

例如:

你是一个 AI 课程助教。
请根据下面资料回答学生问题。

资料:
{{knowledge_context}}

学生问题:
{{question}}

这里的 {{knowledge_context}}{{question}} 就是变量。

程序在调用模型前,会把变量替换成真实内容:

资料:
[资料1] 文档:第四天讲义,片段:2
LlamaIndex 负责把文档切分、向量化并写入 Milvus...

学生问题:
LlamaIndex 在我们项目里负责什么?

模板变量要设计清楚。

常见变量有:

变量 含义
{{question}} 用户本次问题
{{history}} 历史对话
{{knowledge_context}} 知识库检索结果
{{student_name}} 学生姓名
{{course_name}} 课程名称
{{output_format}} 输出格式要求

变量不是越多越好。

变量太多会让提示词难维护,也会让不同场景之间耦合太重。

18. 从环境变量到数据库的演进

我们可以把提示词管理分成三个阶段。

第一阶段:写死在代码里。

ChatMessage(role="system", content="你是一个 AI 课程助教")

优点是简单。

缺点是每次修改都要改代码。

第二阶段:放在环境变量里。

AI_CHAT_SYSTEM_PROMPT=你是一个耐心、专业的 AI 课程助教

优点是不用改代码就能调整部分内容。

缺点是缺少版本记录,也不方便多人管理。

第三阶段:放进数据库。

prompt_template
prompt_template_version

优点是可以管理版本、记录使用情况、支持回滚。

真实项目一般会逐步从第一阶段走到第三阶段。

课程里先让大家理解这个演进过程,不需要一开始就把平台做得很复杂。