提示词和系统模板:Modelfile
每次都重复写提示太蠢了
你的应用每次调用模型都要手工拼一长串系统提示:你是某某公司的助手,只能依据资料回答,遇到不知道就说不知道,回答要分点,末尾加免责声明……拼一次无所谓,问题是这段东西要在每个接口、每个脚本里反复出现。改一个字,全代码库搜索替换,还总有一两个角落漏掉。线上跑着的是旧提示,花一晚上找为什么不生效。
Ollama 给了一个正经的解决方案:Modelfile。它把提示词、采样参数、模型基础这些配置打包成一个文件,一行命令构建成一个新模型,之后调用只报模型名字,所有东西都自动带上。Docker 用户应该倍感亲切,Modelfile 的设计就是冲着 Dockerfile 来的,语法、构建命令、镜像分层的概念一一对应。会写 Dockerfile 的人十分钟就能上手。基础模型相当于 FROM 的基础镜像,PARAMETER 和 SYSTEM 相当于 RUN 和 ENV 那类构建指令,构建出来的新模型像一个叠了配置层的衍生镜像,底层权重共享不重复占地方。理解了这个血统,后面所有行为都能预测。
这一章讲怎么用 Modelfile 管理你的提示和参数。它不涉及训练,只是把现有模型重新包装一层配置,成本几乎为零,但工程整洁度直线上升。对知识库应用来说,这是把 ol-0304 里那套拼接提示固化下来的标准动作,也是从demo 走向产品途中绕不开的一级台阶。
Modelfile 长什么样
先看一个知识库助手的完整 Modelfile,字段含义逐行解释:
FROM llama3.1
PARAMETER temperature 0.3
PARAMETER top_p 0.9
PARAMETER num_ctx 4096
PARAMETER stop "<|start_header_id|>"
SYSTEM """
你是公司内部知识助手,只能依据提供的参考资料回答。
若资料中没有答案,请明确说不知道,不要编造。
回答时分点陈述,并在结尾附上资料来源编号。
"""
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>{{ .System }}<|eot_id|><|start_header_id|>user<|end_header_id|>{{ .Prompt }}<|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
FROM 指定基础模型,可以是官方镜像名,也可以是本地已有模型或者一个 GGUF 文件路径。PARAMETER 是采样和推理参数,稍后单独讲。SYSTEM 就是你那段系统提示,注意这里用的是三个双引号包裹的多行字符串,所以中文内容里可以随意换行。TEMPLATE 是对话模板,控制 SYSTEM、用户消息、历史消息如何拼成最终输入,一般直接从基础模型复制默认模板再按需微调。
除这几行外还有几个常见指令。ADAPTER 用于挂 LoRA 微调权重,这边暂时用不上。LICENSE 和 DESCRIPTION 纯粹是元信息,方便分享时别人知道这模型是干嘛的。MESSAGE 可以预置几轮示例对话,做 few-shot 示意时很有用,比写在 SYSTEM 里更符合模型的对话格式。
MESSAGE 的实际写法如下,给模型一个示范什么是想要的回答方式:
MESSAGE user """年假可以转让给同事吗?"""
MESSAGE assistant """资料中未提及年假转让相关条款,无法回答,建议咨询人事部门。"""
这种示范比在 SYSTEM 里写一百条规则都直观。模型看着示例照着学,回复结构立刻就贴过来。注意示例要精,三四轮足够,塞多了真正提问时它可能继续自问自答给你演对话。
写完文件,两行命令搞定构建和验证:
ollama create hr-assistant -f ./Modelfile
ollama run hr-assistant 新人第一年有几天年假
构建过程就是把配置叠加到基础模型上生成一个新条目,几秒钟完成,不强求显存。之后无论是 ollama run 还是 API 调用,报这个名字就自带全部配置。
构建完先别急着上线,跑两步检查。第一步 ollama show hr-assistant,输出里的 System 字段确认你的系统提示被正确写进去,参数列表逐项对一遍。第二步问一句日常问候和一句业务问题,看回复风格是不是你定的那个味。两步都过了再往产品里切,小事但救命。
参数那些事儿
PARAMETER 行是 Modelfile 的另一半灵魂,几个关键参数必须有手感。
temperature 控制输出的发散程度。零就是每次都选概率最高的词,回答稳定但机械;一以上开始放飞,同一个问题每次答案都不一样。知识库场景的理想值是 0.2 到 0.4 之间,要的是准确陈述而不是创意发散。写诗的应用可以调高,检索问答千万别。
top_p 是另一道采样闸门,和 temperature 各管一段。它从累积概率的角度截断候选词,零点九表示去掉累积概率达到百分之九十之后那些边角料。两个参数会打架,一般定一个动另一个:temperature 固定 0.3 时 top_p 常用零点九。
num_ctx 设上下文长度。默认往往只有两千零四十八,RAG 场景塞不下 top 5 片段,加到四千零九十六比较稳妥。注意它不是越大越好,具体取舍在 ol-0402 里有专门分析,这里先记住它会占显存。
stop 定义停止符。模型生成时碰到这些字符串立刻收尾,用来防止它自问自答没完没了。llama3 系列用 <|eot_id|> 这类特殊标记,其他模型的停止串不同,抄默认模板最保险。
还有一个偶尔能用上的参数 seed。给定一个固定整数,temperature 大于零时同一段输入会产生完全一样的输出。写自动化测试或者做提示对比实验时这个参数就是救命的:今天改的提示和昨天的对比,变量被钉死后差异才好归因。生产环境别开它,否则同一句话所有用户得到同一个答案,撞答看起来很蠢。
| 参数 | 作用 | 知识库推荐值 | 调高效果 |
|---|---|---|---|
| temperature | 发散程度 | 0.2 到 0.4 | 更有创意,更不稳定 |
| top_p | 候选词截断 | 0.9 | 保留更多低概率词 |
| num_ctx | 上下文长度 | 4096 | 能装更多检索片段 |
| num_predict | 最大生成长度 | 512 到 1024 | 回答更长,耗时增加 |
| repeat_penalty | 抑制重复 | 1.1 到 1.2 | 减少车轱辘话 |
num_predict 限制单次回答的 token 数,设小一点能防止模型一言不合写小作文,也保护首字延迟。repeat_penalty 专治同一个词翻来覆去,调到一点一到一点二之间最常见。
这些参数的感受最好自己摸一遍。拿同一道知识库问题,temperature 从零到一每隔零点二问一次,你会亲眼看到回答从刻板到放飞的全过程:零度时像在背标准答案,到了零点八同一个问题给你三种不同的正文开头和两套互相矛盾的建议。这种实验做一次,温度对你的意义就不再是个数字了。生产环境请保持低温,创意留给别人的应用。
写好系统提示的几条原则
Modelfile 只是容器,装的内容才是灵魂。系统提示写得好,模型表现能跨一个档;写得差,参数调出花来也白搭。几条原则值得刻在脑子里。
第一条,角色越具体越好。一句你是助手等于什么都没说,写出你是公司内部知识助手、回答对象是新入职员工、回答风格是简洁专业,这样的信息才有营养。模型和你我一样,被告知受众和场合后输出质量自然不同。
第二条,把业务背景也写进去。只说你是知识助手,它不知道公司多大、有没有多地办公、业务线上有哪些高频痛点。多塞两句背景,比如公司是做工业设备贸易的、员工常问的是差旅与报销问题,相当于把回答的世界观先立起来。措辞不用讲究,信息密度要够,SYSTEM 这点预算值得花。
第三条,规则用命令式短句。不要解释为什么,直接说不要编造、分点陈述、引用资料编号这种短促明确的指令。大段因果论证式的提示反而容易稀释指令权重,让模型抓不住重点。
第四条,给反面示例。光说不要编造模型未必理解边界,补一句如果资料里没有,请直接回答不知道就清楚了。告诉它越界时长什么样,比空洞地劝它守规矩管用得多。
第五条,约束输出格式时连标点都写明。你说请用 JSON 格式回答,十个模型有十种 JSON。要么在 SYSTEM 里放一段完整的示例 JSON 让它照着填,要么直接放弃自由格式改用固定模板问句。RAG 助手默认是大白话加编号列表,真要上 JSON 就别给模型任何自由发挥的空间。
第六条,模板和 SYSTEM 分工。经常变的内容(时效性说明、当前用户角色)应该放调用时的 prompt 或模板变量里,稳定的角色设定才进 SYSTEM。别把每次都要变的临时信息焊死在 Modelfile 里,那样改一次模型构建一次,累不累。
写完 SYSTEM 别忘了回头验收。拿十道最难的用户问题连问三遍,看回答是否稳定符合设定,有没有原本规则写着却没执行到的项。没生效多半是措辞太绕被模型忽略了,缩成短句再试。提示设计本质是迭代手艺活,写一版直接上线的基本都在后面还债,区别只在还多久。
把四条原则落在一个具体例子里对照看看。反面写法是:你是一个人工智能助手,请尽力帮助用户解答各种问题。这句等于什么都没说。正面写法是:你是公司内部知识助手,服务对象是新入职员工;只依据检索到的资料回答,资料未覆盖时明确说不知道并建议联系人事部门;回答用分点式,每点不超过两句话,结尾注明资料来源编号。后者四要素齐备:角色、受众、行为边界、输出格式,模型读完该干嘛怎么干嘛清清楚楚。多花的只是两句描述,换来的是回答质量肉眼可见的稳定。
打包、分享与版本管理
Modelfile 最大红利是可分享、可版本化。文件本身是纯文本,往 git 里一丢,团队每个人的本地模型配置完全一致,再也不会出现你这儿正常、同事那儿胡言乱语、一查发现系统提示不一样的低级事故。
实用的做法有两条。其一,Modelfile 文件按用途命名后统一放在 models 目录下,commit 时连同基础模型名、构建日期、负责人一起写进说明文档。其二,线上服务器用 ollama pull 拉远端仓库里的同名模型替代手工构建,当你的 Modelfile 经过验证发布后推送到私有 registry,部署就是一条命令的事。团队规模再小,这两步做了长期都省心,出问题时谁改了什么一翻提交记录门儿清。
需要对配置做小修改时,别直接覆盖旧 Modelfile。改完构建一个 hr-assistant-v2 跑对比,评测稳妥了再切换标签。模型实例很便宜,保守点的版本策略让你随时回滚,远比在线上裸奔强。
顺带一提,团队协作里这套文件也能参与评审。Modelfile 是纯文本,合并请求里改了几行 SYSTEM 一目了然,产品经理都能审。相比之下在平台后台改配置按钮的做法,谁改了什么什么时候改的,几个月后没人说得清。把提示词当代码管理,是 LLM 应用工程化最容易做到也最容易被忽略的一步。
还有一处容易卡住的细节:基础模型更新后要不要重建。llama3.1 出新版本你 pull 下来了,基于旧版构建的 hr-assistant 不会自动跟着更新。老老实实 ollama create 一次,注意看 TEMPLATE 有没有变化。少数情况下新版本改了对话格式,原模板不重建会出对答错乱,线上出过这种事。
常见问题
SYSTEM 提示里的中文会被截断或者乱码吗
不会,前提是你处理好编码。Modelfile 保存为 UTF-8,Ollama 完整支持多字节字符。真正可能踩的坑是三引号的解析:内容里不要连续出现三个双引号这种组合,另外建议少用奇怪的半角符号,万一碰上某些解析器较老的版本,老实用全角标点最稳妥。构建后跑一句测试验证 SYSTEM 生效,比排查编码玄学快。
改了 Modelfile 之后重新 create,旧模型会覆盖吗
同名 create 会直接覆盖旧条目,没有确认环节。这既是好事也是坏事:持续部署很顺滑,但线上环境误操作就回不去了。养成版本化命名的习惯,hr-assistant 覆盖前留一个 hr-assistant-20260926 之类的影子。另外 create 不会影响已经跑着的进程,但下次请求就会用新配置,灰度切换的机会几乎没有。
一个 Modelfile 能写多个 SYSTEM 吗
语法上允许多行,但写多个 SYSTEM 不会叠加,后面的会覆盖前面的。想拼多段系统提示就在一个三引号块里一次写完,想换分身就再建一个 Modelfile。踩过的人不少,以为两段都在生效,一查只留了一段,排查起来全是时间成本。
为什么我设置了 num_ctx 但提示还是报超长
num_ctx 是上限不是自动伸缩,提示加历史如果超过这个数,Ollama 会按策略截断最早的消息,RAG 场景下甚至连你拼的参考资料都可能被削掉一截。另外某些模型有训练时的原生上限, num_ctx 定得超过它性能会劣化。解决思路只有两个:控制提示总长度,或者选一个上下文天生就长的模型。这一段在 ol-0402 有展开,包含显存占用估算。
如何让多个业务线共用一个模型但提示不同
为每个业务线各建一个 Modelfile,FROM 指向同一个基础模型,SYSTEM 各写各的,构建成不同名字的模型。底层权重只存一份,配置层几乎零开销,这是 Modelfile 设计的分身术。别在一个 SYSTEM 里塞所有业务线的规则互相打架,分身后清晰度和效果都更好。
Mistral 系的模板能直接抄来给 llama 用吗
不能,两家对话格式不同。llama3 用 <|start_header_id|> 这类特殊标记,Mistral 用 [INST] 和 [/INST] 包裹用户消息,Qwen 又是另一套。SYSTEM 里抄错了对话格式,模型接收到的实际上是乱码一样的拼接,表现就像提示完全没生效。不确定的时候跑 ollama show 模型名 看看默认 TEMPLATE 长什么样,照着抄而不是照着别的模型抄。
构建好的Modelfile 模型在别人机器上怎么复现
把 Modelfile 文件分享给对方,对方本地 pull 好基础模型后再 create 一次即可。文件比模型小几个数量级,传输零负担。要更省事就把构建好的模型推到私有 registry,对方直接 pull 名字。前提是基础模型一致,所以 Modelfile 开头 FROM 行最好写带版本号的完整镜像名而不是 latest,latest 这个标签哪天更新了,你的复现就再也对不上。
